# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. Telia pulled the dress over her head and down her body, smoothing the pale fabric thoughtfully. She felt something different this morning. Anticipation. She wet her hair and left it down to dry in long, spiral curls. She slipped on a pair of sneakers and left her one-room cabin, waving to the group under the oak tree. Elada called good morning and Kofu, the unicorn king, bowed his head respectfully. Telia started down the forest path. Though it was morning, warm afternoon sun and the sweet, heavy scent of pine needles enveloped her. Her shoes were silent unpon the deep carpet of old vegetation that paved the path. She turned a corner in the path and sensed a presence. A familiar dusty, male musk, a calloused, gentle hand on her shoulder. She stood still, not turning her head. Hello, Harri, she said lightly. Hello, Tizzy. You've been waiting for me, haven't you? She turned, smiling. What makes you think that, my friend? I could feel your call across the worlds. You are very strong. His voice was low and pleasantly gravelly - just rough enough to make goosebumps rise along Telia's spine. He wore baggy adventure clothes, but they only accentuated the strong, solid body under them. She touched his waist lightly, slid two fingers loosely behind his belt and felt the slack, looking up at him with one eyebrow raised. You've lost weight. I hope you're eating enough. He grinned lopsidedly. Being a hero is hard work. Her hand was still at his waist. She looked up at him silently, her hair a curly golden-red halo about her face. Her eyes were wide, doe-like, silently asking. He nodded ever so slightly, an echo of Kofu's bow. He walked in a close circle around her, his chest brushing her shoulder, his hand brushing her hips. His breath on the back of her neck, then his strong hands on her waist, his arms around her and his mouth on hers, open and ardent. She arched her back, responding with her hands laced in his hair, her soft body against his. The kiss lasted the length of a lifetime, or an afternoon, or an instant, and when they parted, Telia smiled. Harri, why don't you visit more often? You know how much fun we'd have. This world is full of adventures! He smiled ruefully. I wouldn't want you to tire of me. Every hero needs such true support and longing; if you were ever satisified, you wouldn't call so strongly. And anyway, you have responsibilities that preclude my taking up so much of your time. You know that deep down, don't you, Tizzy-girl? She sighed, looking down at their hands entwined between them. Of course. She looked back up with a little smile. I always enjoy your visits, Harri. I know. He stroked her cheek. I must go, Talespinner. Will you return again someday or must I admire you from afar for years on end? Her hero smiled his Indiana Jones smile and said, Trust me. She laughed, and when her laughter had finished, he was gone. Smiling softly, Telia savored the warm glow of the memory of his presence and returned to the oak tree to play poker with the giant Brumbar and Elada. Telia retired to her room. Just as she crawled under the covers, there was a knock and the door opened. A tall blond young man dressed as a space pilot walked in. Hey, Telia! How was your day today? Telia sat up, wrapping the covers around her bare torso. Hi, Javid! Oh. I had a visit from Harri, at last. She sighed expressively. Javid chuckled, sitting on the edge of the bed and brushing the curls back from her face. He whispered in her ear, Did you finally get to kiss him? She hit him lightly and laughed. Not that it's any of your business. Not my business? Why, who do you think convinced him to pay you a visit? Why do you think he was so cooperative? Telia glared up at her friend and alter-ego. Like I can't create my fantasies on my own! You would never violate so perfect a hero by manipulating him yourself, my dear. And don't worry - anything he did was all of his own volition. He quite likes you. Oh, shut up, you pathological liar. Anything to keep me happy! Of course! She crossed her arms sulkily over her breasts, scowling. Well? Does he really like me? Javid grinned. I wasn't there when he arrived. You tell me. Telia's scowl vanished and she lay back again, stretching her arms over her head, sighing and smiling all at once. Javid laughed and threw the covers over her face. I'll take that as a resounding yes. Good. I knew you two would get along. He patted the covers approximately over her head and turned away. Telia sat up. You're leaving? Yep. Gotta finish this other delivery. I'll see you around, though. Okay. Take care. And Javid - thanks. You don't know what it means to me. He paused in the doorway, his back to her, chuckling. Oh, yes, I do. Subtle waves of pleasure and happiness were spreading invisibly from the little room and out into the world beyond, seeding it with new growth, butterflies of hope, canflies multiplying and willsprites hopping with new energy. It's the least I could do, princess. He shut the door behind him and Telia pulled the covers back down to her chin. The lamp went out, and she slept. Telia. Telialorethania! Wake up! You are needed here! She sits up quickly, looking up in surprise at the one who called her name. Javid. The young man grins widely and runs a hand through his hair. That's more like it, Princess! You've been missed around here. It's time to start taking better care of your kingdom. He lowers his voice, glancing towards the open door from which morning sunlight pours into the room. You better stay for a while this time or you may lose it entirely. Puzzled, Telia looks around the room and then at the young man she had called Javid. What he said both rang a bell of familiarity and made no sense whatsoever. Javid stands and walks over to a wardrobe and throws open the doors. Dust billows out and he wavs his hands, coughing. He snatches a plain blue dress from a hanger and shakes it out vigorously. Here. He hands her the dress and strides to the door as she slowly swings her legs over the edge of the bed. He pauses in the doorway and looks back at her unmoving form. Well, what are you waiting for? Get spinning, Princess! The words stab at the cobwebs in her head, sweeping some of them aside, and Telia draws the dress over her head quickly and jumps to her feet. She runs over to the door and out onto a plain dirt path. Thick green grass grows on either side of the path from her door, conspiring with the bright sun and dazzling blue sky to make her forget her awakener and stop in her tracks. The world outside her door is overwhelmingly beautiful. No, it's brilliant. And so. Birds and butterflies flutter through the air between the forest behind her room and the giant oak tree that stands alone before it. Small creatures hop through the grass and pause in their travels up tree trunks. At her appearance, the lively sounds of birds and trees stops and the animals stare. She stares back, wide-eyed. Then, with a joyous rush, the sounds return, sweeter than before and somehow exultant. Though they give no further sign that they notice her, Telia feels distinctly conspicuous as she takes a few tentative steps forward, turning about to see her room and its surroundings. Oh, dear. On the outside, the one-room cabin looks even smaller than it was on the inside, but what makes her gasp is that the entire structure is covered in many layers of tangled vines and tall bushes. There might once have been windows, but the surface of the cabin is only visible in glimpses through the overgrown greenery. All around the door, to either side of the dirt path, piles of chopped-up vines lie, obviously cut away from the door. I was in there? Shaking her head, Telia turns back to the path and follows its beckoning up to the oak tree. The dirt almost seems to push at her feet, urging her on. As she steps beneath the great sprawling limbs in the shade, she feels a stir in the air like an intake of breath. She places her hands on the enormous brown trunk. The tree speaks. Telia can't believe her ears. She looks up into the green-gold of the sunlit leaves. What did you say? Leaves rustle, and from the whisper of the wind comes a sighing voice. You brought back the sun, Telialorethania. Welcome back. The sun. Telia tips her head back wonderingly, gazing up into the swaying foliage. Yes. It had little reason to rise without you. This was a new voice, cool and feminine. Telia turns quickly, sucking in her breath again. What a splendid woman! Her mouth open, Telia takes several moments to get past the thought, How can this be? This woman is - is - an ELF! Where am - what is - who are you? Her brain races ahead of herself. Are you the leader - uh, Queen around here? Looking a little shocked, the woman holds up a hand. Oh, no, my liege - I am not the leader, I am simply the Queen of my people, who owe allegiance to the Talespinner, as do all the folk of this world. The- uh, Talespinner? A troubled line between her brows, the woman nods and bows. I am Elada. Ah. And you are the Talespinner. Oh? Telia tries to ignore the feeling that she is in some sort of bizarre dream. She isn't sure what normality would look like, but she knows it wouldn't feel like this. Perhaps we had better slow down. You have been away for a very long time. Ah. I have. Yes. Elada motions for Telia to follow her. They walk deeper under the giant oak, which towers over them like a natural wooden cathedral or a brown and green skyscraper. On the far side of its mighty circumference, there are steep stairs cut into the living wood. Elada begins to climb, long gown trailing slightly, and Telia, after a moment's apprehensive glance up at the distant green-gold reaches of the branches, follows. The wood is almost warm on Telia's bare feet, and the action of climbing helps her think. I woke up in a place that feels familiar, like an almost-forgotten dream. I'm following an elf woman up stairs in a giant talking tree- This tree can speak? Yes, Elada replies. When it has something to say. It is the First Tree. First Tree? The first tree created in this world. Ah. When? Will I awaken from this dream any time soon? This is no dream, Talespinner. You are home at last. Home? Telia looks around, breathing deeply the tree- and earth-scented air. The staircase had wrapped around the tree somewhat, and she looks down at the vine-covered cabin several stories below them. The dirt path leads from the cabin to the oak, and then continues into the forest. She can almost picture what is beyond the first row of trees. Why can't I remember? They reach a landing, a smooth spot in the center of the trunk where several main branches split in different directions, and Elada sits on the edge of the landing, overlooking the view away from the cabin. Telia plops down a yard from the edge and looks at Elada expectantly. I don't know, the elf queen says at last. Perhaps because each time you have come you have only stayed a short while. Perhaps because there was so much time between visits. But Talespinner, that is why you must stay with us this time! Don't leave us again! This world will never grow without your presence. It will stagnate, or worse - rot away bit by bit until there is nothing but a wasteland of dead dreams and a desert of abandoned stories. Telia keeps her lips firmly shut and fights her utter disbelief at this story. Stay? In this bizarre place? It can't grow without me? What kind of baloney is that? At least promise you will look at everything before you choose to leave. Promise you will let us show you what is possible before you reject it. Whoa. I'm not rejecting anyone or anything yet! I'm just a little confused, that's all. Sure. Sure, I'll stay and find out what this is all about before I make any decisions. If this is just a dream, I'll just wake up anyway! Elada and the tree are silent, and Telia blushes. I mean, no offense, not like you're just figments of my imagination or anything, but this is just a little strange. I mean. Okay. I'll stay for now. I don't know where I'd go back to - or how - anyway. Wind rustles around her like the sound of leaves clapping, and birdsong rings out like a hallelujah chorus for a few seconds. Telia grins. Elada nods. Thank you, Talespinner. Telia follows Elada back down the enormous tree trunk, shaking her head. I just can't believe this is all real! How long did you say this world has been here? Our history records almost 2500 years. So who made this place? How did you and all this get here? The seed, or basic essence, of this world was formed by the One, and it was given to the Talespinner to develop. What you see around you is what you've done with it so far. Telia looks around the base of the great oak. Beyond the reach of its leafy limbs, a forest of lesser trees is on all sides. The cabin sits alone, its lush green overcoat scarred where the door was cut free. The sky is blue, the sun dazzling. The birds sing sweet chirrups and the breeze rustles in the trees. I guess I'm pretty good at this, huh? This is the most beautiful place I've ever seen! Elada pauses, then smiles. She says, somewhat sternly, No, Talespinner, you did NOT create the natural beauty that you see around you. The earth and sky, trees and grass, and most of the animals you find here came with the seed you were given. Oh. And who made the seed? It was the One who created this beautiful world, the One called the Father of All Things. And he gave it to you that you could then shape your stories in it. The One? I'm sorry, but I don't get it. The One what? The One who creates all things, in this world and uncountable others. The One who was before the first thing was, who is where all things are, and who is yet to come. The One who sees and knows all things - Sounds like a busy guy. I get the picture. Elada stares at Telia, frowning slightly. Oh, never mind. So, then what have I done around here? Well, you made the cabin, for example. And the willsprites are your creation. The city Delmere, the parking lot where the squirrel's quest begins, the pool of dreams, the chameleon people - those are all yours. Now Telia stares. Show me! Elada nods. As you wish. We will start at the pool of dreams. A voice like a bell is singing. Echoes seem to ring and ripple through a forest caliginous and deep. Telia tries to breathe, but the air seems thick and clogs her throat. Awe and terror seem to collide and form a new emotion, unnamed. With a gasp, she wakes, to find her bedcovers tangled around her arms and head. The dream is gone. She tumbles from the bed, laughing. It’s a day again! Pulling on a dress from the pile in the corner, she snatches her sneakers and throws herself out the door and into the embrace of the morning sun and chill. She feels like singing, but fears that her voice could not possibly do justice to the thrill of morning in this land and quenches the urge. The blades of grass, the tree limbs and leaves, the lemon and poppy sunrise clouds, it all seems to be shouting at her. Shouting what, though? Something brilliant, obviously. Something scrumptiously wonderful. Something important, of course. At last, she turns away from the riddle of the wordless message. I’ll get it eventually. If it’s that insistent, it will come clear. Yesterday she went to the pool of dreams and watched part of the story of the rose and the stone. She didn’t get very far into the story yet, but feels a strong pull to return and continue it. Something about it touched deep into her soul, for some reason. She waves to Brumbar, who is sitting under the First Tree, and follows the forest path under the trees. Reaching the pool of dreams, Telia scrambles down to the pool’s rim, curious to explore it in closer proximity. As Elada pointed out, the best place for story viewing was certainly the overhanging rock lip – but Telia thought she recognized patterns or letters in the stone she glimpsed through the foliage surrounding the pool’s edge. As she walks up to the pool, her eyes on the overgrown stonework, she sees a ripple of light in the water and turns her head quickly, stepping up to the edge and looking down. A shimmering shape seems to recede into the pool’s depths, but when she blinks both the ripples and the shape are gone. A brief shiver runs up her back. And then, with mercurial ease the moment is forgotten and her attention returns to the stonework. She kneels at the edge and lifts the fern fronds aside, revealing rough-cut old stone, slate-like and gray. And definitely inscribed with runes of some sort. Dirt covers some of the inscriptions, and Telia scoops up handfuls of the chill water to wash the stone clean. Brown clouds spread from her edge of the pool through the black water, billowing as if from some underlying current. At last she has cleared several stones, and finds a few words she recognizes. She plops down, rolling onto her belly and staring down into the pool. A small white glimmer dances beyond the swirl of brown and black, and Telia hears an echo of a bell-like voice in her mind. Tentatively, with her heart pounding irrationally, she slides one hand into the water, reaching toward the glimmer. I wouldn’t get that close if I were you. Telia turns quickly, startled. Water splashes from Telia’s hand across the boots of the tall woman who stands next to her at the pool’s rim. The woman is lean and long-boned, and her hair reddish dark brown, curly and wild on top, with two long, thick braids which hang to her waist. She reaches down, palm up. There are faded scars crisscrossing the wrist and arm. Telia takes her hand and is pulled swiftly to her feet, stepping away feeling awkward and overwhelmed. Besides her height and strength, the woman is quite handsome and has a bearing that touches Telia with awed admiration. The woman bows. I am Tiichan, First Protector. I’m, um, Telia. Well met, Talespinner. The stern lines around Tiichan’s face relax into a warm smile. Yeah… Well, I was just looking into the pool and… So… Does anyone ever swim in the pool? The water seems pretty clear, it just looks black because the rock is black and the water so clear, and… So why shouldn’t I get close to it? And, oh! First Protector of what? Tiichan’s right eyebrow rises and her grin widens. Full of questions, aren’t you? I am Protector of the land. Protector of the heart. She pauses, looking into the distance for a moment, and then shrugs and her eyes meet Telia’s. No, people do not swim here. The Talespinner told us never to touch the water of this pool or to wander near it. Telia’s eyes widen. Why? She never told us why. Telia considers this for a moment, recalling the inexplicable shiver she felt looking deep into the pool. Could her past self had simply been afraid of dark water? Not able to swim? Or did she know about some pool-lurking monster that Telia couldn’t remember? Tiichan begins climbing back up the rock to the ledge, and Telia follows. Did she – uh, did I – uh, the Talespinner ever go near it? Not any further than this ledge. She did her dreaming from here and I never saw her go closer. Oh. Telia looks down into the pool. The dirt is nearly gone, leaving the water dark but glittering. As the sun clears the treetops, it spills onto the clearing around the pool. Good luck with your dreaming, Talespinner. The tall woman was already striding away, her long limbs muscular and pleasant to watch. Wow… Telia murmurs. No more shapes tease her from the pool’s depths, and she enjoys the story undistracted. The story of the young beauty who meets the beast under the mountain… it pulls at Telia’s heart. But even so, Telia falls asleep late in the afternoon as the sun sinks behind the western treetops, and dusk falls unnoticed around her. Elijah Slocum's voice came in sobs. He shook his head and said, I don't know what you're talking about! How can I confess to a crime I don't even know about? The two detectives said nothing. Slocum looked at one man, then the other. He said, I want my attorney. You can't hold me here without letting me see my attorney. Detective Robert Smith bent down and pulled his pant leg up, revealing a small pistol in the harness on his ankle. Look here, colored boy. It would be a shame if you were to make a grab for that, and I had to shoot you. Detective Morgan Davis tapped his partner on the shoulder. Bob, come on now, you don't want to hurt the man, do you? Smith slapped Slocum across the face. Hard. Turning to his partner, he said, That answer your question? Davis gave Smith an angry look, then he spoke to Slocum. Excuse us a moment, will you? Smith followed Davis out the door. Half an hour later, they were gone, and Slocum felt the pain return to his bladder. He shouted, Hey! Somebody! I have got to take a leak! Davis, who was just down the hall, turned to Smith. You think he's ready for good cop bad cop yet? Smith shrugged. I dunno. He hasn't peed himself yet. If they gave a trophy for iron bladders, he'd get one. I say we should wait another ten minutes, and then use the phonebook trick. Howcome, Smith said as he sipped his coffee, I always have to play the bad guy? Davis said, Just turn your watch around and try not to laugh this time. Ten minutes later, Smith grabbed a new cup of coffee and led Davis to the interrogation room. Slocum was perspiring heavily, and his face was contorted with pain. You ready to confess yet, colored boy? Slocum ignored the insult, and said, I'd like to carefully consider that, after you untie me from this chair and take me to the restroom. Smith said, I don't think we have time to do that. He rotated his wrist outward, tipping his hot coffee into Slocum's lap. Slocum screamed, then urinated. Aw, geez, will you look at that. Now if you hadn't been so obsessed with that bathroom, I wouldn't have spilled my coffee trying to check my watch. Davis shoved Smith aside and said, Mr. Slocum, you just hold on there. I'm going to get a pitcher of cool water. He left the room. Smith knelt down, so his face was inches from Slocum's. That was purely an accident, you understand? When Slocum ignored him, Smith grabbed the hair on the back of Slocum's head. You listen and you listen good. I don't like child molesters. You understand? I'm going to make you fry a lot worse than that coffee burn, if you don't sign that confession. Davis entered the room. Smith, leave him alone! Shoving Smith aside once more, Smith poured the pitcher of water onto Slocum's lap. Then Davis said, Why don't you take five, Davis, and come back in here when you can be civil? Davis swore under his breath and left the room. Davis paced between Slocum and the door. Look, Mr. Slocum, Smith is a real dedicated cop. I can't control him all the time. I'm trying to help you, but you’re not helping yourself. What? What do you want me to do? Sign a confession to a crime I didn't commit? You guys are violating my rights. You want me to tap dance for you, next? What you got out there, a mob and a tree to hang me from, until I confess to killing someone I never saw in my life? Davis let out a muffled laugh. No, what I want you to do is sign a confession so I can get Smith off your back. He's pretty anal, you know, and until he gets that confession, he's going to be like a pit bull. Lord, he probably threatened you while I was gone to get the water. Slocum nodded. Yeah, he threatened me all right. Now look, I want to talk to an attorney. I need to make a phone call. Just sign the confession, Mr. Slocum. Your attorney can overturn it later. Happens all the time. Then why is it so important for you to have it? Not me, Davis said as he motioned toward the door, him. Just then, Smith flung the door open. Your five minutes are up. He looked at Davis. All right, you pansy, did you make any progress? Davis said nothing, and Smith snortled. I didn't think so. Well, I am tired of waiting on this piece of trash. Slocum glared at the Davis, but said nothing. Davis walked over to Slocum and said, Colored boy, this is your last chance. You sign or I get rough. And your friend here better not interfere. I've got my rights, and you are vio… Before Slocum could finish his sentence, he felt the side of a phone book strike the side of his face. Man, stop that! Look, I'm dying of thirst. Can you give me a drink? That's all I want, is a drink of water. Davis said, I'd get you a drink, but I'm afraid that would make my partner very angry. He hates it that you're not confessing. But I’m not g…. Smith hit Slocum three more times with the phone book. Slocum cried out in pain, then said, Man, are you crazy? You think someone won't find out what you're doing? Smith said, It won't leave a lasting mark. I can hit you with this until you die, and nobody will be able to tell. He hit Slocum six more times, before Davis grabbed his arm. That's enough, Smith. If you just give Mr. Slocum some time to think, you won't need to hit him like that. The two left the room. Ten hours later, they returned. Slocum was still sitting in the same chair. He had urinated again, and it had dried. Smith hit Slocum with the phone book again. I hate the smell of nigger piss. Can’t you control yourself? This just proves you're an animal. An animal capable of killing a little girl. The two detectives then escalated the torture. Fifty-three hours and twenty-one minutes after his interrogation began, Slocum gave in. Sitting in his own feces, Slocum signed the confession with an unsteady hand. Davis read the document, and set it down. He picked up the phone book and gave Slocum another hard swat. I knew it! Now I have proof you did it. The detectives took Slocum to a drop-off point, where a patrol car picked him up. The officers found a typewritten note, with Slocum's signature on it. The confession was enough to get Slocum arraigned. The judge set his trial for a day four months away, and set bond at eight hundred thousand dollars. When Slocum complained of police brutality, the judge said, I hear that all the time. You may file suit, Mr. Slocum, if you think you can prove anything. This court has heard all the testimony it needs to hear from you today. Chapter Two Bobby Holt smiled when the guard brought Elijah Slocum to the visiting room. Slocum sat on his side of the glass, and looked, red-faced, at his boss. Bobby, a Yale graduate, slipped into his smooth country manner. Many corporate predators had met their doom by underestimating him when he did this. This time, he hoped his gentle voice and diction would sooth his tormented friend. Mornin' to you, 'lijah. How they treatin' you in here? I only been here one day. Thanks, Bobby, for coming to see me. Bobby nodded. Suppose you tell me how you got arraigned. Shoot, suppose you tell me how you got arrested. I haven't had a chance to talk with you since you were arrested. Bobby, you and me go way back. Bobby nodded. You took me from way down and gave me a chance to be somebody. Bobby nodded again. Elijah's voice cracked. I am so embarrassed, I don't know whether to be angry or to die. Now don't go dyin' on me, 'lijah. No, no, you know I won't do that. I didn't do a sex crime, and I certainly didn’t kill that little girl. But here I am, and I signed a confession, and people think I did it. It's even in the newspapers. Bobby nodded. I saw an article or two. Confessed killer. Now, 'lijah, why'd you go and sign a paper sayin' you done that? Bobby cocked one eyebrow. They didn't coerce that out of you, did they? Elijah told Bobby Holt the story. And I was so thirsty, I thought I was going to die. I also knew they didn't care if I died or not. So I took my chances. But you know I didn't do such a heinous thing. Bobby nodded. My friend, knowin' and provin' are two different things. Yes, I do know. What I got to do is prove it. How you gonna do that? Bobby looked at his fingernails for a moment, then he stared Elijah straight in the eye. I won't fail you, Elijah. You just keep the faith. After the two men spoke another five minutes, Bobby Holt stood up and said, Well, my friend, I have a manufacturing division to run. I'll put out some sort of official statement later on. Don't worry about a thing. Elijah shook his head. Sorry, boss. The only thing I can do is worry. Bobby gave his friend a weak smile. Don't. Look, Elijah, if it'll make you feel any better, I'll tell you what I'm working on. But you have to promise me you won't tell anybody. Nothing can make me feel much worse than I do right now. What's your plan, boss man? First, I'm posting bail. I took out a second mortgage on my house, and that'll cover about half of it. So I just have to come up with the other half? No. I have a margin account. Also, I'm going to appeal to our company's board to put up $600,000 on your behalf. It may take some time, but I think I can persuade them. Wait a minute, Bobby. That's six grand plus four grand, plus whatever you get from your margin account. I only need eight grand. I don't get it. Are we talking, you know…. Elijah silently mouthed the word scam. Bobby laughed a little at first. Then when Elijah gave him a dirty look, he burst out laughing. Elijah couldn't help but grin. Hey, Bobby, if you haven't noticed, my life is on the line. What's so funny. Bobby brought his laughter under control, and said, I'm not running a scam. Hell, no. What I am going to do is hire a private detective. First, they are going to find out who framed you, then…. Bobby Then what? Never mind. Who are you going to hire, that firm that does our industrial work? Yeah, Martin and Santana. I know Kimberly Martin personally. I'm sure she'll take the case if I ask her. Yeah, well, good luck. You must not know her too well. She retired from the detective business just a few months ago. Kimberly squinted through her swollen eyes. Sweat trickled from her long red hair to the floor, but some of it ran into cuts on her face. It was hard to see, but they made her look. The man in front of her spit into a Coke can. We’re back, you stupid nigger-lover. Been back five minutes now and you aint ast if we kilt her. Kimberly could almost feel her hands wrap around this bigot’s throat. Except her hands were tied to the same chair her legs were tied to. She said nothing. She’s your sister, dammit! Don’t you want to know? The man slapped her. Look at me when I’m talking to you. She raised her head slightly. The man’s nose was inches from hers. We aint kilt her yet. He put his can between her nose and his, then spit into it. But we’re fixin’ to. That’s all up to you. Kimberly’s head dropped as far as it would go. The man flew into another rage. He’d flown into several today. Are you going to talk or not? He slapped her left breast hard, and the pain from the blow made her back arch. The man smiled. So, where’s that damn computer? You want me to hit you again? Kimberly’s mind drifted past the whiney, hateful voice, past the pain that wracked her body. She floated back to the weeks before her kidnapping. She pictured the man who would carry her away from here. Or would he? She wished she had made love to him. She wondered if she would ever have the chance again. A single whispered word wafted out onto the night air. Sinners. The tall man who had uttered the word blinked a light once. Another light blinked from the other side of the lane. In the middle of the lane sat a car, holding two teenagers. The light-blinkers crept toward the car, then stood just to the rear of its unlocked doors. Kevin’s lips gently brushed Beverly’s, then he shoved her away. In the window was a face covered with a black mask. Beverly looked stunned, then she saw the visage in the window. Ninja? Is this a joke, Kevin? You’re scaring me. Before Kevin could say anything, both car doors flew open. Arms reached in. Both Kevin and Beverly screamed. A guttural voice filled the night. Sinners! We shall make you repent! Beverly felt one arm wrap around her waist and another around her chest. She leaned forward and bit into a hairy arm. It was enough to make the man drop her. Beverly lurched forward, stumbled, and got back to her feet. She heard a man bellow something, and then she heard a loud cracking sound. Kimberly’s hand stroked idly across her husband’s belly. Do you know what I like best about fighting with you, Jerry? I love the way we make up. He didn’t say anything. She lifted one of his eyelids. Are you asleep, baby? Jerry sighed. Do you have to do that with my eyelids? Jerry, where you asleep or just pretending to be? Flatly, he said, I was only dreaming about you. She laughed. You liar. She wet the tip of her finger and put it in his belly button. You were probably dreaming about Pamela Anderson. She put her nose on his, and said, How come you’re so quiet, tonight, Jerry? Don’t I excite you any more? She kissed him lightly, almost playfully. It’s just a peaceful night, that’s all. And I’m really tired. Do you mind if we just go to sleep? He kissed her and said, You are gorgeous. Good night. Beverly ran just inside the edge of the woods, and stopped. She peered from behind a tree and looked at the car she had just run from. She saw two men carry Kevin’s limp form toward a van parked maybe fifty feet away. One of the men stumbled, and almost dropped Kevin. Beverly suddenly pulled her finger from her mouth, and looked at it in disgust. She wiped it dry on her pant leg. Under her breath, she said, What in the hell is going on? Almost in answer to her question, a twig snapped. Then another. Beverly’s finger went back into her mouth, and she began quivering like a trapped rabbit. Margie heard her son’s door open, but she kept reading her novel Little Jeremy came out of his room, his dog Sandy behind him. Without saying a word, he climbed onto the couch and sat next to his mother. She looked up from her book and said. Isn’t it past your bedtime, young man? Jeremy nodded. Then she asked him, So what are you doing up at this hour? Sandy was growling, and she woke me up. I think there’s someone outside. She laughed. You liar. You were probably dreaming about Pamela Anderson. She put her nose on his, and said, How come you’re so peaceful, tonight, Jerry? Don’t I excite you any more? It’s just a peaceful night, that’s all. And I’m really tired. Do you mind if we just go to sleep? He kissed her and said, You are gorgeous. Good night. Beverly ran just inside the edge of the woods, and stopped. She peered from behind a tree and looked at the car she had just run from. Two men were carrying Kevin’s limp form away. She suddenly pulled her finger from her mouth, and looked at it in disgust. She wiped it dry on her pant leg. Under her breath, she said, What in the hell is going on? Almost in answer to her question, a twig snapped. Then another. Beverly’s finger went back into her mouth, and she began quivering like a trapped rabbit. Jeremy came out of his room, his dog Sandy behind him. Without saying a word, he climbed onto the couch and sat next to his mother. She looked up from the novel she was reading and said. Isn’t it past your bedtime, young man? Jeremy nodded. Then she asked him, So what are you doing up at this hour? Sandy was growling, and she woke me up. I think there’s someone outside. Margie sighed and motioned for her son to sit on her lap. Jeremy, she’s not growling now. Do you hear her growl? They musta went away, he said. Beverly waited, and heard nothing. She decided to go for help, wherever that might be. She took two steps away from her tree. A man stepped in front of her. Hello, little sinner. She took in his features-tall, slender, stern. She turned from him and ran. Then she felt it-something sharp making its way into her body. And here is the synospis of the entire book: Synopsis of Nightrage A novel by Mark Lamendola Nightrage is a 90,000 word mystery-suspense novel. The story opens with a kidnapping in the middle of the night. Five years ago, Kimberly Martin brought a white supremacy group to its knees and foiled their plot to assassinate black presidential candidate Corey Powers. In the process, she won the contest for her lover’s heart—against her own sister. Shortly thereafter, she founded Martin and Santana, a detective agency. Kimberly’s husband, Jerry Santana, is a management consultant (he’s also a martial artist, body-builder, gun collector, and computer buff). Jerry helped Kimberly start her business, with the understanding she would never again do the dangerous work she did when she took on the white supremacists. Her agency, now with a large staff of detectives, has cachet with the local police. Most of their cases involve insurance investigations and other non-criminal work. Occasionally a project crosses the line into the world of criminal intrigue. When that happens, Kimberly does not work the case—an employee does. Since the agency started, Kimberly’s played it safe. Until now. When a long-time friend of Kimberly’s parents asks her to find his missing niece, Kimberly personally takes the case. In the process, she risks everything important to her. As the investigation continues, she finds herself navigating through a labyrinth of twists, turns, and interconnections. Kimberly’s first employee, Roger, is a physically attractive gay man in his early 40’s. He’s a frequent guest in the Martin-Santana home, and in fact, occasionally stays there in a room set aside for him. Life gets ugly when Roger comes home to find his lover’s mutilated and bloody corpse. Things escalate from there, and Kimberly eventually finds herself a target of a cult leader’s wrath. While Kimberly is embroiled in a fight to save all she’s worked for, including her marriage to a man she adores, an eight-year-old boy asks her to find his missing dog. His life is pathetic, and Kimberly is moved. Despite her problems with the kidnapping case and tragedy in her personal life, she cannot tell him no. The bond between the boy and his dog is strong enough to reach deep inside Kimberly’s heart. The bond between Kimberly and the boy goes beyond friendship. Kimberly’s involvement with the missing dog takes her deep into a wooded area, where she has her first conflict with a rap-sheet superstar (MacGruder). This puts her temporarily in league with the Knight—a self-appointed judge and executioner. The battle with MacGruder is brief, but it paves the way to disturbing revelations that shed light on the case of the missing girl. In fact, those revelations shed light on several recent events that speak of a consuming rage. Kimberly Martin loses her marriage, her house, then her job. Jerry has admired her for years, but respected her marriage as inviolable. Upon news of her divorce, he attempts to bring their relationship to a deeper level. Kimberly has always found Jerry attractive. Bound by her marriage, she never pursued her feelings for him. Bound by self-doubts brought on by an abusive spouse, she cannot see this man has deep and abiding feelings for her. Kimberly knows she is beautiful in outside appearance, and has endured many a leering glance and suggestive remark. Her ex-husband, however, made her feel she has little else to offer. He haunts her physically and emotionally when he finds out she is seeing Jerry. Her lack of self confidence compels her to prove she is more than a pretty face and curvy body, that she is worth an enduring relationship. While using Jerry’s modem, Kimberly stumbles onto a white supremacist computer Bulletin Board Service. She and Jerry discover a plot to assassinate Corey Powers, who is a military hero and a black presidential candidate. Kimberly invents Bleach, a male BBS persona who convinces the other BBS subscribers he is an old hand in the assassination business. Kimberly brings news of the BBS to detective Richard Gallatin. Unknown to her, Gallatin - through an alter ego - is on the BBS network and a leader in the group that plans to assassinate Powers. He is not happy to see her report, and responds accordingly. He sets a trap for her. Jerry’s friends rescue Kimberly and her look-alike younger sister Gina from separate groups of Gallatin’s cohorts. Kimberly’s fate was far worse than Gina’s, and while Kimberly recuperates in the hospital, Jerry takes Gina to Dallas to meet with Powers. Gina, forever the spoiled child, intrudes on Kimberly’s relationship with Jerry. Circumstances put both women temporarily in Jerry’s home. It is inconceivable to Gina that Jerry would prefer one sister over the other. The tension between the sisters runs likes thread through the story. Kimberly has a hard time believing any man would really love her, and she desperately wants Jerry to love her for the rest of her life. Her own insecurities combine with her fear she may lose Jerry to Gina. Kimberly has no choice but to go for the gold. Over Jerry’s objections, then later with his support, Kimberly goes undercover to foil the assassination plot. With her hair dyed, shortened, and put in a bun, she adopts a different voice, posture, and walk. Even Jerry does not recognize her. She infiltrates the BBS boys as Bleach’s chosen instrument. The plan for her to fake an assassination attempt is designed to identify the supremacists who join in the plot, and indict them on conspiracy charges. While Kimberly is away as Carol, Kimberly’s predatory ex-husband, Dave, finds Gina alone. Dave has lusted after Gina for years, and finally corners her. Gina’s unusual method of brutal defense against rape clearly shows she is more than a simple tart. Is her ingenious defense enough? Does she prevail? Gallatin and Kimberly cross paths again, while she is undercover as Carol. It is his job to teach her how to use a sniper rifle. Gallatin, a married man, hides his real identity from Carol, although Kimberly knows who he is. Gallatin notices Carol’s figure, despite her frumpy appearance and ill-fitting clothes. Her meek demeanor and naiveté lead him to believe he can control her, use her, and have his way with her. Kimberly becomes trapped between accepting his advances as Carol and advancing the cause for which she went undercover. To which self is she true? Other characters enrich the story. For example, Sal Posito is an Italian immigrant whose wife, known only as Mama Posito, dotes on Jerry. Several incidents show Sal’s intense love for, and almost religious devotion to, his wife. Sal believes God sent him an angel to love, and he makes that his purpose in life. The Positos have two adult sons who work in the family business. They comically complain about their Italian heritage. The one son says, Hey, don’t talk no Dago to us, Jerome. We hear that all day long in the business and it gets worse at home. If not for the great food, I’d quit being Italiano. When the Positos are closing up their store, robbers break in. Papa Posito, enraged by the sight of seeing his angel struck by one of the robbers, attacks the men. Mama Posito is able to get to the pistol they keep at the store, but she is too late to prevent Papa from being grievously injured. When Jerry visits Papa at the hospital, Mama tells him the story of the glass elephant. Her narrative reveals an incredible deepness of love, and it causes Jerry to look deeply into his own heart. In so doing, learns something about himself. Is Jerry really in love with Gina or with Kimberly? Does he love both of them, or does he discover he cannot love either? Does Carol - Kimberly’s alter ego - sleep with Gallatin, a man Kimberly detests? What does Kimberly’s ex-husband do that gets him on the front page of the newspaper? What is it about this news that gives Kimberly the most terrible embarrassment of her life? Does Corey Powers survive the staged assassination when it does not go off as planned? What happens at the end when Gina, with her hair now dyed the same color as her sister’s original auburn, approaches Kimberly with a shotgun in her hands? What dark secret does Kimberly discover about her sister, shortly after Gina raises the barrel of the gun? Mudwoman is a full-length mystery novel. Near the end of a long car trip, the rain lets up. Alan Bowers slams on his brakes when his headlights catch the figure of a woman. She is in shock, disoriented, nude, and covered in mud. The only thing she says is, Help me. When Alan attempts to bring her to the hospital, a pickup truck nearly runs him off the road. Shortly thereafter, the hospital provides the woman (Linda Baxter) with clothes and releases her to go with the police. When the police take Alan and Linda to the place where Alan found her, they follow her tracks back into the woods, where they discover a pit in which Linda had been buried. They find the remains of what must have been several hundred pounds of ice. Then they visit the spot where Linda was jogging - the last thing she remembers doing before climbing out of the pit. There, the police find tire tracks matching those of the vehicle that nearly ran Alan's car off the road. Alan takes Linda to her apartment, and a locksmith lets her in. Linda's heart stops when she finds her jogging shoes, shorts, and top neatly folded. She finds her home office ransacked. Linda begins to reveal a glimpse of her past, a past she wants to forget. Why the line of demarcation in Linda's life? She was a professional killer, in a special military unit. Now she and another woman are partners in an engineering firm, and she likes the peaceful life. With the discovery that her abductor rummaged through her home office, she fears both she and her partner Debbie are in danger. When she and Alan visit the police department and then the coroner, they hit on the connection: Linda survived abduction by a serial killer. The last person to fit the pattern was found with her uterus missing. And her skin was frostbitten in the middle of summer - ice! Linda's new job: go door to door in selected neighborhoods of possible suspects, disguised as a salesperson. Linda visits her psychiatrist, Dr. Jenkins, who deals with her trauma by saying men are concerned with penis size and women are fixated on breast size. He angers her by insisting this is the root of her problems, and dismisses her mudwoman story as something she fabricated. He tells her she is unwilling to date, because of this, and she reminds him of what she went through in the Marine Corps, and they are the ones who paid for her engineering degree. Again, he dismisses her as being sexually hung up. He ends the session by making a pass at her. The abductor, Pete, is an idiot savant. When Linda comes to his neighborhood, he recognizes her. He tricks her into getting out of view of the police van, and - through trickery - renders her unconscious and then binds her hands and feet. He destroys the wire she is wearing. He is not used to being in the company of a woman, so he takes his time with Linda and becomes fascinated with her. He comes to the realization he must free her, and agrees to do so as soon as the sun is down. Though she is locked in this man's house until sunset, she feels she has reached deep within him. Unfortunately, this is when Pete's masters show up. They administer a drug that knocks Linda out, then they take her to the airport in a wheelchair. When she comes to, she finds herself an unwilling employee of a brothel. The second time customers visit her room, she kills all three of them, using a crude weapon she fashioned after the first visit. She escapes onto the streets of Singapore. There, she assaults a policeman, gaining both a weapon and some money. Meanwhile, Pete defies orders and flies to Singapore to try to rescue Linda. Alan and Debbie find Pete's house, and they find a copy of his travel itinerary in his trash. Shortly after that, Linda finds a print shop and sends a fax to Debbie. All four people converge in Singapore, and Pete reveals the existence of a child prostitution ring, run by the same people who'd flown Linda to Singapore. The ring is headed by Yamamoto. Alan and Debbie convince Linda to go back to the United States, and Pete insists on escorting her. Alan and Debbie arrange to assist the local police for a short time. At the airport, a group of men attack Debbie, and Pete dies trying to protect her. She escapes, discovers Alan and Debbie were tricked and taken captive. She finds them and together they escape to the United States, home to Yamamoto. The investigation is rapid and fraught with danger. Clues, deadends, new clues, one lead barely tied to another, and a lot of legwork lead Linda and Alan to their ultimate goal. But it's not an easy road to run. Moves and countermoves characterize both sides. The pace becomes almost dizzying as Linda and Alan close in. Capture, rescue, escape, and pursuit follow one after another, until the final fatal showdown with Yamamoto. Who is Yamamoto? None other than the wife of Linda's psychiatrist. Yamamoto's true insanity make for a grisly climax to the action. In the end, Linda and Alan prevail. The book closes thusly: Linda put her head on Alan’s shoulder, and put her hand inside his shirt. Alan? Yes, dear mudwoman? Do you think we can have children? I’d like to get started right away. Little mudchildren. Sounds wonderful. What brought this on? I think you’ll like the pitter-patter of muddy little feet. It is time. The story opens with a brutal attack on two teenagers parked in a lover's lane late at night. The girl's uncle calls his friend, the heroine (Kimberly), in the wee hours. His call wakens her and her husband (Jerry). Kimberly, a nationally famous detective, agrees to help. While Kimberly is on the phone, you subtly see the love between Kimberly and Jerry. For example: Jerry came back in the room, and handed Kimberly a glass of water. She stroked his chest with the back of her hand before taking the glass. She sipped from it while she listened to Pete. We go to another scene: a discussion between the abductors, one of whom is The Knight: The Knight stretched his arm and examined his cut sleeve. That little sinner put up quite a fight. The ensuing conversation reveals a mind so twisted, even one of the abductors reacts in fear: Gary finished packing his things. He walked out of the room without further word. The next day, he gave notice at work and put his house up for sale. Who is our heroine? Five years ago, Kimberly Martin brought a white supremacy group to its knees and foiled their plot to assassinate black presidential candidate Corey Powers. In the process, she won the contest for her lover’s heart - against her own sister. Shortly thereafter, she started Martin and Santana, a detective agency. Kimberly’s husband, Jerry Santana, is a management consultant (he’s also a martial artist, body-builder, gun collector, and computer whiz). Jerry helped Kimberly start her business, with the understanding she would never again do the dangerous work she did when she took on the white supremacists. Her agency, now with a large staff of detectives, has cachet with the local police. Most of their cases involve insurance investigations and other non-criminal work. Occasionally a project crosses the line, and an employee works the case. Since the agency started, Kimberly’s played it safe. Until now. Kimberly personally takes the kidnapping case, and in the process puts her marriage on the line. As the investigation continues, however, that is the least of her concerns. Kimberly’s first employee, Roger, is a physically attractive gay man in his early 40’s. He’s a frequent guest in the Martin-Santana home, and in fact, occasionally stays there in a room set aside for him. Life gets ugly when Roger comes home to find his lover’s mutilated and bloody corpse. Things escalate from there, and Kimberly eventually finds herself a target of a cult leader’s wrath. While Kimberly is embroiled in a fight to save all she’s worked for, including her marriage to a man she adores, an eight-year-old boy asks her to find his missing dog. His life is pathetic, and Kimberly is moved. Despite the urgency of the kidnapping case, Kimberly becomes increasingly involved in this little boy's life. She does find his dog, by using the talents of a dog she babysits. Kimberly and Jerry have been playing secret games to gain the dog's affection, each to impress the other. Here's an excerpt: Roger said, I’m just taking today off. When are you coming back? Tonight? Yeah, tonight. Listen, do me a favor. Kimberly is borrowing Vicious today. Vicious? Jerry laughed and said, Luigie’s horse that poses as a dog. Roger said, Oh, Precious. The dog is partial to oysters. Oysters? That’s right, oysters. Now look, Kimberly gets really horny when she sees how much the dog worships me. And as you know, I like her horny. As much as possible. I’m not sure I follow. There is a green bowl I keep wrapped in plastic. I rub my hands all over it before Vicious visits. I also tape some of my hair clippings to the underside. Then I feed her one of her favorites. The dog. Roger sounded slightly irritated. I get it, I get it. Alright, I’ll take care of it. You’re a good man, Roj. We’re boarding now. Gotta run. Roger hung up the phone, and began rummaging through the cupboards. He was amazed that, in a home where both partners and a frequent guest cooked often, things got put back in the same place. He easily found what he was looking for. OK, let’s see. Green bowl, oysters. Blue dish, the little ravioli’s and a spray from Kim’s beige perfume bottle around the edges. The phone rang, and Roger answered it. It’s me, Jerry again. I left a rawhide next to the recliner in the den. Don’t touch it too much. It’s got my scent all over it. I meant to tell you about it earlier, but. I’ll take care of it. Thanks, Roj, you’re a peach. Roger hung up the phone. Let’s see, Jerry’s rawhide by the recliner, and Kim’s chew toy next to the window sill in the weight room. Don’t touch. Geez, if these two ever had a kid, I can only imagine. Kimberly does find the boy's missing dog, deep in the woods adjacent to the boy's trailer. The dog had chased and treed a man later identified by police as MacGruder, a known sex offender. The boy's mother recognizes him as the man who had harassed her at the diner where she worked. The battle with MacGruder is brief, but it paves the way to disturbing revelations that shed light on the case of the missing girl. In fact, those revelations shed light on several recent events that that speak of a consuming rage. A Knight rage. The Knight - the self-appointed prophet of God (and judge/executioner) who kidnapped the niece - realizes the connection between Kimberly and the boy. So he kidnaps him in an attempt to neutralize Kimberly as she continues to close in on him. Kimberly enlists the help of the boy's dog to locate him, and Kimberly conducts a stealthy rescue. Later, when the Knight finds out Kimberly is the one who injured the evil MacGruder to the point the man was immobilized in the hospital, he begins to question his idea that Kimberly is the Beast Woman. Very near the end, the Knight rescues Kimberly from extreme danger, only to suffer death at the hands of his own group's leader. The book closes with a banquet, at which Kimberly steps down as president of Martina and Santana, offering the job to Roger. Roger makes an offer, too: When he said that, Rhonda rose from her chair. People turned to look at her. Then Roger said, It is time. Rhonda, will you marry me? In the Library, Adam had pored over some Gregory Bateson material. Bateson had been a favorite with Adam and Ganya years before and Adam had once taken some pleasure in fighting with one of Bateson's former wives, the one with the forked cane. He considered himself in the right, naturellement. Precisely, he thought. City people were being asked to relate a system which confronted them as unalloyed hostility. Drivers and pedestrians alike caught in a dance of death. Pedestrians were not sure whether the wanted to simply flee or somehow to find a way to live that would be less hostile, less poison, less inhuman. Drivers took little pleasure in their daily round of incessant waiting, road rage and sheeplike acceptance of the reality until hostility boiled over and you got the quintessential Boston Driver. The driver thought the city should be his. The pedestrian should think the city should be his. Or hers. Or theirs. Both knew deep down that they were prisoners of an economic reality over which they had no control. And this fact alone was justification for Adam's Boylston Street Plunge. Afterward he would sing of it. The July sky of southern Mississippi glowed orange, yellow, and purple as the sun continued its decent below the western horizon. Sam Cristo stepped off the only bus leading into or out of the fringe town of Edgewood. The heavy breeze blowing from the Southwest brought little relief from the scorching heat compounded by the thick humidity. According to the local weather broadcast, which had been blaring through the crackling speakers of the bus driver's aging radio, the temperature had reached 105 degrees at its peak on this Saturday. With sweat fluidly running down his forehead, Sam surmised that the impending sunset would do little to negate the intensity of the afternoon heat. As the bus made its departure, Sam inhaled a cloud of dust kicked up by the bus tires spinning against the loose dirt road. Examining his surroundings, Sam realized that no one else had stepped off the bus at this stop. Rumor had it that Edgewood was not the sort of town that people ever visited, nor were the native citizens likely to leave the town at any point in their lives. A lifetime's journey had brought Sam to this shadow of a town, a municipality too small to appear on any map. People who wanted to find Edgewood would run into great difficulty if they were unfamiliar with the surrounding towns, some of which were also missing from most maps. The bus had dropped Sam off beside a bench that rested in front of a mini-mart called Ed's, which apparently doubled as the town bus station. Sam found it surprising that a bus would even bother to stop in such a diminutive town – he had expected to wind up in a larger town where he would have to ask for directions to Edgewood. Sam looked past Ed's mini-mart toward a saloon called Last Stop, which sat on the mini-mart's right. To the left of Ed's was a small combination post office/police station with a single patrol car parked in front – there was no mail truck in sight. In a town this small, he guessed, the mailman likely walked. Behind the three buildings, Sam could make out the top of what appeared to be an extensive forest. The area in which Sam stood seemed to be the center of town. Small, rancher-style homes lined the rest of the street on either side. Next to the bench was a road sign with the name Main Street on it – how original, Sam thought. With no additional roads immediately visible, Sam concluded that Main Street was the only road in town. He recalled a sign as the bus entered Edgewood, which had read Edgewood – Population: 95. The 95 appeared to be a slightly different shade of black than the rest of the sign's letters, as if the number were changed on a regular basis. Sam's skin began to burn under the intense heat so he decided it was a good time to get inside. He walked toward the Last Stop, determined to down a few cold beers and quench his increasing thirst. Sam had never been much of a drinker but tonight was a special occasion. After all, he was here to find the murderer of his beloved Jeanette. Detection seemed to be playing an odd role in the Panflickian life. Back at the Frisbie Memorial on Louisburg Square, the young Mr. Silliman closed the portfolio he had been perusing and picked up a Boston phone directory. It took him a few moments to locate the number he was seeking in the Government section. Soon he was speaking in hushed tones to a functionary at a local branch of the IRS. This soul informed him that there was indeed a percentage paid to those who reported subsequently proven instances of tax fraud. Silliman licked his patrician lips and unloosened his tie a tad, unbuttoning his blue Brooks Brothers buttondown collar. Everything he surmised about the odd person who stood between him and Serena told him that he was of the old hippie school. He had probably never paid a dime in taxes for whatever he and Mr. Pirhana had managed to win in Las Vegas. With major gusto Wendell B. We will give you the 1st chapter. In it we will identify the main character, that character's unique ability or traits, and set off in A direction. The following chapters and where we go in them are up to you. The direction is yours to choose, how the characters get there and what they do is up to you. The changes, the people, the situations that occur are dependant upon your imagination. Just remember that some thread of the plot line must remain, however thinly preserved, to the original character AND your chapter should end in some way to allow your fellow cyber novelists to carry on with. The main characters may not die and NO obscene, gratuitous foul language will be tolerated. We are a site for the entire family. As the Uber-Editor of this tome-to-be I have absolute and final say and any material that I deem to be inappropriate will be removed and deleted. The chapters will be posted in some manageable number in the order in which they arrive. If, after a month or so, you don't see your submission it means that you didn't make the cut. Don't be discouraged; read where we are and submit again. And again. And, above all, have fun with it. Hi there! I've been on the net since 1997. When I first started, I didn't even know how to surf! My Macintosh was 6 years old then. Slow, slow, slow. I saw the potential to reach people worldwide at almost no cost. Wow! But, how? I didn't have a web site. I didn't know how to set up a site. So, I worked with only email. It was tough. Frustrating. Then, I decided to set up a web site - without knowing how. I figured, that first you need to have a web host. So, I got one. I thought, they could make changes for me on my site. They did, but. Frustrated, I quit them and found another one which I use today. Now, since I didn't want to pay for someone to make changes to my site, I decided I had to learn html. At first, it looked difficult. I searched the bookstores to find a way to learn how to do it. I found Elizabeth Castro's book: HTML. Wow! She made it easy to learn. So, I spent many hours learning. Experimenting. It was fun and frustrating. Now, it is easier. Then, I asked myself. How can we earn income online? First, I figured I had to sell something, because I had no web skills. So, I decided to see if I can sell Hawaiian Chocolate Macadamia Nuts. After getting some help on my forms, I did set up a page and amazingly, I made a few sales. The net worked! I still sell them. Now, I wanted to promote my line of health and nutrition products. I saw the potential for that online. So, after months of testing. I put up my own web site. I called it: Earn Income Online! Sounded good to me and exactly what people want. Ever since I did that, and submitted my URL to thousands of sites. I eventually generated over 800,000 hits in 1998. Gathered thousands of leads. What a problem! Now. My biggest advantage is that I am a distributor for a company that has Distribution Centers in over 40 countries. What this means is that I don't have to ship any products outside of Hawaii. This meant that I could live in paradise and have a global income! Wow! On the net, I figured that I save on. Long distance calls. Postage. Printing and brochures. Sending out info massively in seconds. Training by partners worldwide - daily! All this meant less expenses and more profit. So. What a fun business! In 1997, after a lot of struggle in figuring out how to do it. And, countless hours - I achieved over $30,000 in online sales volume in my business. As I began to add more partners, and they did the same. A 1000% increase! I still can't believe it. Finally, all my efforts, patience, persistence, and belief paid off. Now, in 1999. More and more people are interested in joining our partnership. We are teaching them daily. We have templates of our web pages for our partners to use. We help them set up their web site - the web host sets up their pages. Then, we show them how to generate leads by supplying them thousands of sites for submission. What really astounds me is that. The net is the future of the world. It is fast becoming a way of uniting the people of all countries. Thanks to the net, we have thousands of friends worldwide. They know how we look like from our photos on the net. I would say, it is the greatest thing ever to happen to me in my life, besides marrying my beautiful wife, Nina. Our business is growing faster and faster - monthly. We are having so much fun. I didn't share all the many details in building my online business in this message. All that is covered in our daily online Entrepreneur Trainings - FREE to our business partners, worldwide. I am totally committed to help others do what we do online to earn income. I feel we have the #1 business opportunity on the Internet - for the average person to succeed. Nina and I are blessed in finding a way to earn income online. Nina and I wish you great success in your quest for online income. Of course, you are welcome to join our partners globally if you are serious about online income. By the way, we now have a new Macintosh G3/266 computer which we bought last year that is superfast, thanks to the profits from our online business. Now, the net is turbo fun! If you would like to take a nice, leisurely electronic stroll through the exhibits at the Janet Annenberg Hooker Hall of Geology, Gems, and Minerals in the Smithsonian National Museum of Natural History, just click here for a virtual tour. This takes you on a step-by-step walkthrough of the exhibit hall. A Gem of a Story has been made possible through a partnership among: The United Parcel Service Foundation. The UPS Foundation, the charitable arm of UPS, provides local non-profit agencies with the support and funding necessary to meet existing and urgent community needs. The grants, ranging from US$10,000 to US$100,000 each, are distributed to organizations throughout the United States, Puerto Rico, Mexico, and Canada. Since the program's establishment, a total of US$30. UPS, the world's largest package distribution company and a sponsor of the 1998 and 2000 Olympic Games, employs more than 338,000 people worldwide and provides services to more than 200 countries and territories. UPS revenues for 1996 were US$22. Insite Distance Learning. Insite distance learning is a technology-driven initiative at Ball State University and the Indiana Academy to provide high-quality distance education programming to K-12 and college students and teachers. Programs include the K-12 electronic field trip series, elementary-level language classes, high school Advanced Placement courses, undergraduate- and graduate-level college classes, and professional development for teachers. The Indiana Academy for Science, Mathematics, and Humanities and its Office of Outreach Programs. The Indiana Academy is a state magnet school for gifted high school students located on the campus of Ball State University in Muncie, Indiana. It has two primary purposes. First, the Indiana Academy serves as a residential high school for approximately 300 gifted and talented juniors and seniors from across the state of Indiana. Second, through various Outreach programs, the Indiana Academy strives to stimulate and enable vitality in educational programs for academically gifted students and teachers. The Office of Outreach Programs has established itself as a state leader in distance learning opportunities throughout the nation. The National Museum of Natural History and its Natural Partners Program. The Smithsonian Institution's National Museum of Natural History is one of the preeminent natural science museums in the world, housing millions of artifacts and some of the top scientists and researchers in the United States. The Natural Partners Program, a science-education initiative developed by the museum, provides students with access to vast scientific and cultural resources, using the Internet, CD-ROM, and interactive video-conferencing technology. This approach, called the Electronic Classroom, is the first step toward a new museum without walls. Ball State University, Ball State University Teachers College, and Ball State Teleplex. Ball State was established in 1918 as a state-funded institution of higher education in Muncie, Indiana. Today, more than 19,000 students are enrolled in 125 undergraduate programs, 76 master's programs, and 19 doctoral programs. Its 955-acre campus houses seven distinct colleges and 62 major buildings, including a 1. At its core, Ball State is a premier teaching university - a place where computer and telecommunications technology is used to enhance teaching and learning. The Massachusetts Corporation for Educational Telecommunications. This includes daily satellite broadcasts, computer networking for email and links to the Internet, videotapes, and videodiscs, as well as technical training and content support for participants. The Cambridge-based corporation serves a broad base of educational institutions, non-profit agencies, government services, and businesses. Ruby and sapphire are varieties of the same mineral: corundum. Corundum is aluminum oxide, which is the second hardest material known (after diamond). Though it's relatively common, most corundum is not gem quality. It's normally mined for polishing compounds and abrasives, because of its extreme hardness. Pure corundum is colorless, but even tiny amounts of impurities can create a wide range of vivid colors. If a relatively small amount of chromium replaces aluminum, the corundum is tinted the red color of ruby. Or, if iron and titanium are present, the blue color of sapphire appears. Click on the thumbnail images below for full-size, high-resolution pictures of the specimens. Then click on the Back button of your browser to return to this page. At long last, ecommerce is gaining real momentum. Nielsen Media Research reports nearly 25% of consumers (or 20 million) who logged on to the Internet in the first six months of this year bought something online. A number of product categories - including books, music and electronics - are proving popular Web shopping items. However, one category outshines the rest. It took off so dramatically - even the experts were caught off guard. And many analysts were forced to overhaul their projections for the industry as a result. Of course, I am talking about online travel - the online sale of airline tickets, hotel bookings and travel packages. Datamonitor goes on to say the travel industry will account for 35% of all online sales by the year 2002. When id Software - maker of the phenomenally popular Quake - announced in June its next version will focus on multiplayer gaming, you could almost feel the thunderous applause from the online gaming community. Being the industry leader it is, gamers envision other developers following id down the multiplayer gaming path. Not that it’s going to take a lot of arm-twisting to convince developers there’s a future in online gaming. As we reported in our “Favorite Things to Do on the Web” series, (click for full story), interactive game fever is spreading. According to a study by +Plan, roughly 13% of Net users between 54 and 65 - and nearly a third of all 18-to-24-year-olds - go online to play games. Here’s what they’re finding when they get there: Hot sites for multiplayer gaming. GameSpot rated gaming services earlier this year, listing Internet Gaming Zone, Bezerk and Mplayer among its favorites. Click for full story. Best online games. In its current roundup of multiplayer games, Computer Shopper/NetBuyer gives HeadLand Digital Media’s NetWar high marks in the arcade space for its nonstop action and top-notch graphics and animation. If it's combat in the air you crave, Warbirds 2. Click for full story. What’s next. Over 15 million people in the U. The report says most online gamers currently pay nothing, frequenting free sites. It's a tried-and-true marketing strategy: First they lure you in with freebies. Once you’re hooked, you'll pay. Click for full story. Not all fun and games. More and more girls and women are discovering online gaming. And there are sites devoted to assisting those pursuits - from a Quake Women’s Forum to Da Valkyries - Women Gamers Online. But as FamilyPC reports, women often run into harsh language at many online gaming sites and, it turns out, a fair amount of harassment. Click for full story. How do you see the future of online gaming playing out? Will it get too expensive? Will it become kinder and gentler as it ages? Join our Rants and Raves forum and discuss online gaming issues with other AnchorDesk readers. I was sort of flattered when Jesse came to me and asked if I’d put together a special collection of downloads for today’s issue. I mean, he said I was the only one he could count on to do it right. I thought, “Whoa, time for me to ask for a raise. Occurred to me Jesse may have been making some point about my work ethic, but I chose to ignore that possibility and put together a fun collection for the 9-to-5 crowd. These are game downloads you can play by yourself, quietly, while the rest of the office thinks you’re concentrating intently on your work. I’ve linked more in the sidebar, but among my favorites: Bubble Puzzle 97: Shoot colored bubbles to the top of the playing field using a cannon. But hurry; they need to be removed before they hit the red field. Cool graphics, including the animated bubble faces. BrainsBreaker: Some pretty snazzy features in this jigsaw puzzle game that you work with your mouse or via keyboard shortcuts. You can also create puzzles with your own graphics. Challenge Pool for Win 3. You can control the cue with your mouse or keyboard; see all the shots the way the expert sees them. You know, Jesse may be trying to make a point, having me do this ultimate office goof-off thing. Like it will shame me into working harder or something. Know what? This was actually fun. Didn’t seem like work at all. Use the Easy Download Guide in the sidebar if you need help downloading. And for more games, check out the AnchorDesk collection of Killer Downloads. Click for AnchorDesk's Killer Downloads. The Doctor staggered into the TARDIS'es primary Console Room. Struggling against the encroaching unconsciousness, he set the Space/Time craft in motion. At least, he thought, the people of Pisces Australis Nine were safe. The same unfortunately could not be said for Kamelion II, the Doctor's cyborg companion from the planet Xeriphas. Kamelion had given his life to secure the Time Lord's escape. And all the Doctor could hope for now. Slumped against the large six sided console the Doctor sighed deeply. He was very grateful to see the steady rise and fall of the Time Rotor, as the TARDIS had correctly responded to his keyed in command, slipping the two of them into the safety of Vortex. Daleks! Even wounded, his voice possessed its normal, clipped edge. Am I to be dogged for all Time by their continued interference in my lives? He continued to grumble. Why, they're almost as annoying as the CIA of Gallifrey! As the Doctor righted himself to stand next to the wide control center, a hand idly swept across the broad, high forehead to brush back his mop of perspiration damp, strawberry blond hair. His hand moved to his side. Withdrawing it he saw it was covered with blood. This was hardly a surprise to the slightly enigmatic Time Lord, but he was rather annoyed by it. Carefully, gingerly removing his coat of many colors, he let it fall to his feet. Supporting his throbbing side, as best he could, the Doctor grimaced, then, staggered from the Console Room, a single destination in mind, the intimacy and serenity of his private suite. The Doctor had rallied the necessary energies required to, remove the rest of his garments, take a bath, and even to dress his wound, before collapsing face first onto his wide, Victorian bed. Without a sound the Doctor slowly rolled himself over. Slowly up righting himself he smiled at the accomplishment. Then for all his expended efforts he slipped off into a deep, yet fitful sleep. When consciousness returned to him, so did his slightly gravelly voice. Daleks! Though his green eyes were ravaged with pain, the arrogance of the Prydonian was still his; especially in the way he held his mouth. Guess I always knew it was just a matter of time before one of them got off a lucky shot. Simply dressed in a long white nightshirt, with a large, fluffy, multicolored, embroidered cat on its front, the Doctor chuckled, marveling slightly at his ability to make light of his present situation. Actually I am rather surprised I did make it this far in my rather long, if not illustrious career, before one of them did succeed. Briefly he closed his intense. Well, my dear, he whispered to the TARDIS, Quite obviously this is the end of the me, me. But, hopefully, it is but the beginning of something else. He sighed deeply,. For where their is life, Victoria their is. The Gallifreyan's gaze slowly traversed his domain. Huh, wonder who I'll become this time? Snuffling, his nose crinkled. Just as long as I don't turn back into that cricket lad. The Doctor scowled. Never was happy with that one. Chuckling, he began to drift off into the hazy in between, limbo ed existence that always preceded a regeneration. His words became badly distorted. Different. The Doctor inhaled deeply. How strange. At the eve of change, the Doctor had usually found himself among caring, devoted companions. The Doctor managed to arch a brow. Well, almost always, he sighed, recalling the second (and forced) regeneration. But even then, surrounded by the hostile, disapproving High Council, I certainly wasn't alone! The Doctor's sudden, heightened awareness of his isolation, his separation, his distance from home, was beginning to swamp him. Alone. I've never been so. Salt free tears welled in his eyes, and began to slowly trickle down his cheeks. Alone; Doctor? The Doctor blinked, and struggled to focus his sight. A puzzled look swept across his now ashen face. An enormous head shimmered, as it slowly materialized above him. Rassilon? Yes, Doctor, Rassilon soothed. Not alone. Not now, nor ever, but certainly not especially now, now at this time of becoming. The Doctor struggled briefly, then his eyes slowly closed, and he stopped breathing. This gentle, Prydonian Time Lord who had a fondness for kittens, and yellow/brown stripped trousers, was dead. The Daleks, evil machinations of the twisted Kaled scientist, Davros had finally managed to kill him. But not. Though his present body was no longer capable of sustaining life, with a little luck, and the release of a special hormone (lindos) from three special glands in his body, the individual cells of his body could renew. This was called Regeneration: a new chance at life. The huge transparency of Rassilon, hovering over the Doctor, concentrated briefly. Opening his ancient, venerable eyes he smiled, nodding his approval. And thus it begins. In the quiet of the Doctor's bed chamber, surrounded only by the ever present soothing hum of the TARDIS'es systems, and soft evening-times light, this transmogrification unique, slowly unfolded. The change began quietly, invisibly at first from the center most part of the Doctor's body; with ever increasing velocity, and visible manifestations as it radiated outward. The Doctor's lindos was well on its way to working its particular brand of magic. The lindos. The humanoid face and skin blackened markedly. The features of the Doctor's face blurred, like melting wax. The slightly rotund face began to shutter. But then. With the regeneration complete, a much thinner, and slightly younger looking body had replaced the former. The Doctor's breathing, slow and regular began again. All at once the Time Lord's eyes popped open. This time it was pair of hazel eyes that greeted the new day. Eyes that brimmed with keen intelligence, bubbling enthusiasm, and an incredible softness. The MAZER hologram of Rassilon nodded proudly, and slowly, silently shimmered into nothingness. A smile lazily spread itself across the Doctor's new and different face as the Gallifreyan realized a new life span had began. How did the Doctor know this with such certainty? The ever present pain of the last few hours of his former life had vanished. In the very first moments of existence for persona number sever, the Doctor slowly sat up. So. The rejuvenated head turned to the side in puzzlement. What a terribly odd sounding voice you have, Doctor. The Prydonian pouted in the old comfortable manner. Shakily standing the Doctor lightly padded over, in bare feet, to a cheval glass. Smiling at the unkempt reflection, the renegade from Gallifrey chuckled. This was for sure a downsized Gallifreyan who held up arms totally engulfed in the nightshirt's sleeves. The Doctor cackled. And once more to find myself all teeth and curls! The Time Lord grinned widely in approval at this discovery, proudly displaying nearly two full rows of perfect, glistening teeth, tousling the jumbled mass of curls framing the stranger's face. This Doctor's hair was a near copy of the earlier fourth persona's. Though I do recall it having a lot more reddish high lites. Stepping up to the mirror, for a closer scrutiny, the Doctor snarled at the newest in the line. Is that supposed to be a nose? Flapdoodle! The reflection was given a loud, raspy raspberry. How in the seven sons of Delos am I supposed to breathe through something as tiny as those? Sighing deeply, the Time Lord accepted that which was unchangeable. Well, one must take the rough with the smooth, the Doctor waxed philosophically. Unlike the females of our species, generally speaking, we have no choice in how a regeneration turns out. Suddenly the Doctor's features contorted in stark realization. Jumping' Jehosophat! But the Doctor quickly reconsidered. Can't be. Too. Glaring into the mirror this time, the Doctor watched as a pair of small hands began to explore the rather vacuous face. True, the features are very fine, one might even say, delicate, but I am the Doctor! Am I not? Turning from the freestanding mirror Number Seven made Decision One. However, first things first. A shower, a shave, and locate some proper fitting garments from Wardrobe; and a suitable pair of boots from my Boot Cupboard. The Doctor briefly reflected. Maybe Scarecrow's old things can be put into service yet again. The face brightened. As I always say: 'Never throw anything away, Susan'! The Doctor's face soured. Or is it that I always say. It's a mistake to clutter one's pockets, Harry'! Still in the former persona's nightshirt (complete with mostly dry blood stains), the Doctor had just finished picking up the discarded garments of Number Six. Only this time, in reverse order. Turning, the Time Lord headed back into the corridor, the jumble of clothing tucked under one arm. Softly, and a little sadly the Doctor spoke to the bundle of clothing as it was dumped into the built in hamper in a sidewall of the ever arcing corridor. Good night Joseph, old man. It was fun. Must do it again, sometime. Lightly tapping the front of the hopper the Doctor turned and headed deeper into the maze of the TARDIS'es corridor system. The Master was sitting in his own TARDIS his usual pensive, brooding, moody self. On one of his hands was a black leather mitt. Cradling it in his bare hand, the Master glared daggers at the constrictive glove. So soon? This can't be happening to me! Tremas was in his prime when I acquired his body. Somehow I've been double crossed! All at once his expression became venomous. Who else but the Doctor! I can feel his stupid, stubborn interference in this! Yes. I remember it now. Manipulating the Source! And in a later regeneration he was there too on Sarn when I used the Numismation gas to restore my size. The Master further contemplated. What I must do is assure for myself a new secure cycle of Regenerations while removing utterly, somehow, this very persistent thorn from my side. His mind continued to plot his villainy. And if I could somehow include in that plan those shallow, pompous, arrogant, poltroons who call themselves Time Lords. The outlaw Gallifreyan chuckled. That would be delicious! The Doctor sat in front of the dresser mirror, hair still damp, wearing an old outfit that had once belonged to Scarecrow (persona #2). One of the Doctor's hands was resting on a blue and white recorder. Scarecrow's garments fit, more or less, except for the navy colored coat. Why it was ill fitting, especially across the shoulders was perfectly apparent to the Doctor, now. The shower of a few minutes previous had removed all doubt as to what had happened in conjunction with particular regeneration. The Doctor. The Doctor looked at her dazed, disorientated, confounded, reflection. But how can this be? This isn't supposed to happen! The intensity of the reaction startled her. Looking at herself, her lower lip began to tremble. How? Why? She swallowed hard. Of course Gallifreyans come in two different models. She grinned slyly. It's far more fun that way. The fragile smile faded. However, one does not switch over to the other side simply because one regenerates. Her eyebrow arched. I mean, just the logistics of it all. Now that's some engineering. She cleared her throat. Indeed! She shook her head. This has never happened before. Even when people willed it to be. Never. Her voice became full of anger. So why now? Why pick on me? Turning her head slightly she smiled wistfully. If ever I had need of a Zero Room. Realizing what she had just said, the Doctor grabbed up the little wooden flute. The Zero Room of course! The Inter-Council of the High Council of the Time Lords of Gallifrey had hurriedly, if not very un dignifiedly, been called into emergency session. It was a very intense and very stern President Flavia who addressed them. Then we are unanimous ladies and gentlemen. We must send a Panoptican summons to the Doctor. He perhaps better than anyone else, living, comprehends the MATRIX; its indispensability to Gallifrey, and to the greater Universe at large. A mumble, for the most part, one of voiced agreement, echoed round Flavia. The Doctor, dressed in a blue velvet outfit like those worn during the era of Fancy Pants (persona #3) was laying on her bed, on her stomach, playing a hand held video game, as she munched on a sack of jelly babies. Her suite was newly renovated and the bed chamber part of it looked very much like the sleeping quarters used by Queen Victoria during her long reign as Queen of England. Without any bumps, thumps, or other such warnings the Cloister Bell began its sonorous, deep throated gong-gong-gong. The Cloister Bell! She continued to listen, then covered her face with a corner of her blue tweed, caped coat. Oh no, no, no; not today thank you, came her muffled reply. But the bell was insistent. She slowly lifted her head, listening to the bell's doleful peal. Sighing deeply, exhaling nearly a whole mouthful of air, the Doctor reluctantly stood; shuffling towards the suite's exit, muttering to herself. Man the battle stations, Adric. Looks like there is stormy weather ahead. Sighing and plopping her deerstalker cap onto her jangle of curls, she added almost as an after thought. Oh, piffle. In animate steps, the Doctor sprang down the corridor, her Inverness Cape flapping in the breeze of her own making. For the moment the Cloister Bell had stopped its ominous tolling, but that didn't mean a disaster wouldn't be forthcoming. Reaching a point where the corridor made a wide bend to the right, she continued to follow it until she came to a large oval arch recessed in the wall. Fumbling in her pocket she took out her sonic screwdriver. After that, while Joseph (persona #6), the Doctor decided any tool Romana (persona #2) could duplicate, making subtle improvements upon, he could too. So he (persona #6) did. Looking up at the mass of fine gray wires she'd just exposed, she sighed with deep respect at the Gallifreyan technology responsible for its creation. Oh Omega, without question, you were a genius. Each strand of wire contained a light of its own, with different colors traversing each length to and fro. The random winking reminded the Doctor of a Christmas tree, and the total effect was in a word, mesmerizing. She smiled. Hullo main logic. Are you well, sweethearts? Expertly she ran tiny fingers through the wires, and the TARDIS central computer responded to her caressed inquiry by turning the wires a pale beryl. Obviously, nothing wrong with the main logic junction, functioning, she said pouting. This particular puzzle was not to be put together very easily. Tapping her lips with the sonic screwdriver the Doctor began to absentmindedly create a filk song. Junction function, what's your compunction? Putting together bits and nybbles, and bytes, and logically opening and closing logic gates, ever logically. Putting away the sonic screwdriver she took out her wooden recorder, and began to play a few notes of the song she had just created. Taking the cream colored, tasseled flute from her mouth the Doctor leaned the side of her face against the wall, noisily exhaling in frustration. Her expression became one of intense concentration, as she lightly tapped the recorder against her lower lip. Then what ever in the Stripped Zans of Doddiefrax caused the Cloister Bell to toll, anyWHO? As if on cue, the early warning system began again it's bassoon type bellowing. The Doctor covered her face with her hand. Thumping herself on the head with the flute she scolded: Doctor, has anyone ever told you, you have a big mouth! The old Coordinator, standing in the middle of his computer complex, shook his head in disapproval at the screen before him. Toddling over to a different console, his scowl became even more in evidence. Lifting his head, he softly yet very distinctly informed the leader of the High Council. Madame President, we are having a great difficulty locating the Doctor belonging to our Time Stream. Due to our extreme state of emergency, and for your consideration, I have located twelve very clear, very strong tracks of the six past Doctors, and of the six future selves, including the Doctor who calls himself the Valeyard. But our present Doctor is remaining, for some reason, most elusive. Flavia nodded. But we can not call on either his past selves, nor can we call on any of his future selves without setting into motion the gravest of consequences. You must continue your search Coordinator Damon, for the present Doctor, in spite of all this interference in our Time Band. Her eyes narrowed. How difficult, even with the drain on the MATRIX, can it be to find the only Type 40 T. It is a very large Universe, Excellency, Damon said softly. And my old friend has a very long history of itchy feet. And contrary to any of our rules, the Doctor could be almost. Maybe not even in his own Time Stream, or traversing our part of our Time Band as he is supposed to! Yes Coordinator, Flavia agreed with a very wistful smile. We all realize that fact. But you must keep trying. The Doctor will turn up. The President, chosen by the Doctor himself, before she was elected in her own right; paused a long moment. When at last she spoke, her words came out sounding more like a plea. He must. Damon nodded solemnly, returning to his station at his primary console. The Doctor was at present standing over the small, six sided wooden console in the Edwardian Console Room. Most of her earlier personae just ignored the room, a couple of the others just considered it nothing more than an auxiliary control center. The present Doctor however viewed the room in a very special light for like the fourth persona, she was genuinely fond of anything antique, and objects constructed of natural woods and fibers. Still, the last couple of days had seen some changes in the room. Besides the existing three, stained glass roundel, representing the first three personae of the one called Doctor; she had hung a set of three paintings; those of the second triad, Doctors four, five, and six. Additionally three more circular portraits in glass had been set into the mahogany walls : one of Rassilon, one of Omega and one for Falla, the three Gallifreyans most responsible for turning the Doctor's race into Time Lords. And just who had done the paintings, on commission, for her? Why, the master who had done the first three of course. Leonardo Da Vinci. From time to time the Doctor would key press a code into the TARDIS'es central computer via the Console; still very intent on trying to riddle out her Cloister Bell problem. She was well aware the TARDIS was doing her best to tell her something, but the Doctor hadn't yet figured out what that something was. Scribbling down brief, hurried notes in her old leather notebook, laying open on top of the Console, she rubbed her head in puzzlement half-heartsedly punching in a few more coded queries, wishing she had Falla's gift of ciphers. Suddenly a ringing sound was in her head. Her hand went to her forehead to steady the dizzying sensation. She felt overly warm, felt as if parts of her consciousness were being drawn ever outward. Then she realized what it was. The Call. The Call from Gallifrey? Wonder who is interceding for me? Who on Gallifrey knows I'm a female? Nevertheless, squaring her shoulders, she obeyed the ancient homing instinct. Gallifrey, in the constellation Kasterborus, it is then, Doctor. She smiled. After all, who am I to ignore a Panoptican Summons? Her hand moved to set the familiar coordinates. Her eyes began to glisten playfully. No, she balked, drawing back her hand before her fingers could key punch in the final bits of binary code. Too much trouble, she sniffed, sucking on a fingernail. Too many numbers. She shrugged. Of course, not as many as 0101 1010 0100 0000, not squared. Hexadecimal, the Doctor chose, beaming. Much easer. That much resolved, her eyes quickly narrowed. The Doctor slowly pondered her quandary. True, 98 squared is shorter, but I like the sound of five A forty. The subtle way it rolls off the tongue. She broke out into a toothy grin. Decided then. Bending towards the Console a second time, the hex numbers were zestfully punched in. Had she forgotten that in an emergency situation just one key punch of a special button would rubber band her almost instantly to Gallifrey? No. ETA to Gallifrey twenty six minutes, she mimicked K9. Flight path clear. Well pleased, she began to nod. On her way home, she was prepared for whatever Gallifrey had to offer. But would Gallifrey be prepared for this Doctor? That was a horse of a different colour! The TARDIS nosily materialized in a large room in almost the center of the Capitol. The Doctor had arrived. Home! Oh dear, I have spent entirely too much time in England of late. As she opened the TARDIS'es exterior scanner bay doors, and the exterior door in preparation to leaving, dizziness once again returned, but this time it was different. Staggering round the control room she collapsed against a wall beneath the stained glass roundel of Rassilon. Then unknown to her. By the time the Doctor regained consciousness, and shakily stood, seven other Doctors had left their TARDIS'es, and were busy talking among themselves. The Doctor pouted at the scene before her. Looks like I wasn't the only Doctor summoned to Gallifrey. The Time Lords must be in even more direr straits than I. She grinned widely. Boy, are my counterparts ever in for a shock! She chuckled. Not to mention what Gallifrey is going to think! So, seven of us, with me making it eight, she ruminated. Wait a minute. I'm the seventh one! That fellow in the golf sweater is an imposter! Watching her other selves, she felt strangely left out, till she realized. What? Again no sound? Glaring at the mute viewer screen she began to tap her foot in aggravation. A moment later she had decided. Will fix you. Turning to the console in front of her, she patted it affectionately. So, my dear old broken thing, here goes me, again, and again, and redundantly. My stars. Am I never to stabilize this regeneration? Huddled round the eighth TARDIS, its double doors slightly ajar, the Doctors began to question each other. Wonder what has happened to our number seven? Number One (persona #1) asked. His TARDIS was after all, the first to arrive. Norm (persona #8) softly chuckled to himself. Curious, Fancy Pants (persona #3) sniffed at the perfumed air, wafting from the opened TARDIS. Wonder how many more of us will be summoned? We may all be here already, young fellow, Number One interjected. Thirteen selves is the maximum number of lives any Time Lord is given but there is no guarantee of that eventual, total number. Fancy Pants nodded soberly in agreement. Especially, considering our propensity for finding ourselves under the harrow. The Doctors agreed. Well, the way I see it, Joseph (persona #6) said refastening the black and white, cat pin on his Technicolor coat. This seventh one of us has had plenty of time to come out on his own. I agree, Cricket (persona #5) concurred. I say we go in and find out what's keeping the old boy. Good plan, Doctor, Teeth and Curls (persona #4) stated to Cricket, a Mona Lisa type smile on the intelligent, inquiring face. For a second time Norm's eyes began to shine with his foreknowledge. And just what was it that the eighth persona found so amusing in all of this? Teeth and Curls wondered. He sighed. Well, Time would tell. After you, Doctor, Scarecrow (persona #2) bowed politely to Teeth and Curls, popping a grape jelly baby into his mouth. One by one, the Doctors solemnly entered this seventh persona's TARDIS. Once inside it was Joseph, dressed beneath his coat of many colors, in a costume much like Fancy Pants. Speaking first to Teeth and Curls he said: This is the Console Room you used for awhile, isn't it, Doctor? Teeth and Curls nodded. By far the oldest looking and yet in reality the youngest, Number One looked round, well pleased at the dark mahogany roundel walls of the Edwardian Console Room. So it is, he iterated, swinging his elegant cane in the familiar gesture. Grabbing the lapels of his coat he beamed. I must say this Doctor certainly has excellent tastes regarding interior design of control rooms, no matter which one he chooses. Doctor? Cricket's voice reflected concern, his eyes coming to rest on the sprawled, unmoving form of the seventh persona. Teeth and Curls bent down, talking the caped part of the Doctor's coat from off her face. Well. Scarecrow beamed. Who also seems to be a mass of curls and teeth, said Joseph, looking towards the fourth persona, chortling softly, as he tilted back his cranberry fedora. The original Teeth and Curls merely shrugged with a sly, pixie grin. At least he does have a dapper, good dress sense! Fancy Pants snorted. The Doctors laughed. Looking round him, Scarecrow was the first to spy something else. A cream colored, cream tasseled recorder atop the control console. Narrowing his eyes he walked over to the small wooden flute and picked it up. And he plays the recorder! Scarecrow was overjoyed. The other Doctors however were not so enthusiastic. As one they moaned, vehemently protesting their displeasure in one united front. The little man with daisy applique braces, and dark, straight hair just ignored them. He was perfectly delighted. Teeth and Curls lightly touched the side of the Doctor's nose. Is he alive, Doctor? Apparently, just fainted, Teeth and Curls replied, rubbing his chin. Quite probably at the sight of seven other selves! Stepping up onto the console's deck, Number One noticed an additional thing. That is, this later persona's notebook. Running a wrinkled hand across the battered old cover Number One nodded in approval, thinking to himself that this regeneration of self was certainly the one showing the greatest promise. Number One's benevolent thoughts were rudely interrupted by a very annoyed Scarecrow. Well, old chap, don't just crouch there taking his pulses. Do wake him up so we can play a duet for all of you. I mean, he began to stammer. All of us. Removing his own blue and white flute from its special pocket in his dark blue coat, he held both recorders so they could be plainly seen by all. The Doctors protested loudly with strains of: Spare us! Oh no, no, old bean. Bad show! Let sleeping. Scarecrow frowned. Amid the din, Teeth and Curls solemnly picked up the Doctor, carrying her to one of the comfortable Console Room chairs. As he did, something in his brain clicked, and his eyes shot towards Norm as if to say, ('surely not'). But Norm was slowly nodding with a very wry smile. In the Victorian captain's chair the Doctor began to rouse. Daleks! Change! The Cloister Bell! The Call! The words tumbled from her lips in broken strands. She sighed, briefly covering her face. I do believe this is quite enough! This stabilization is proving to be as difficult as the last one was. Joseph nodded with compassionate understanding. Though he would never admit to anyone, just how difficult it truly had been for him. Just how long did you regenerate, my boy? Number One asked with concern. The Doctor briefly computed. Ten days ago. Rubbing her chin she looked across to the voice that had spoken with such sincere solicitousness. Well, hullo, Doctor. A silly grin spread itself across her slender face. You I recognize, at least. Hullo, my boy. Regeneration? Number One politely inquired. Ah, sixth. She felt herself. No. She shook her jumble of locks. Persona number seven still. Norm chuckled to himself. The Doctor turned towards the sounds of Norm's laughter. Personnna number eight, Doctor, Norm bowed. At your service, though you may call me Norm, if you so desire. A very special friend tagged me thusly, and the name just sort of stuck. Something to do with a return to NORMalcy, in the Doctor's lives, I believe. The Doctor smiled warmly. Don't fret my boy, Number One nodded. You'll get use to it. It is after all, the hardest part of any regeneration. The Doctor snorted. Want to bet? NORMaly I would probably agree with you. Norm chuckled. Well, which ever one of us you are my boy, Number One said smiling kindly. I must say, I certainly approve of your method of note keeping, and of the way you've designed this Console Room. He chuckled. I only hope you can get it to work for you. So far it's not disappointed me. She began to beam. Besides, why change a good thing. The Doctors agreed. Anyone else probably would have been a bit puzzled. Now, she began, clasping her hands together. Perhaps one of us. Doctors. Cricket smiled. This future persona's body language was very much like his. So of course he approved. The Doctor continued her scan of her other selves, anxious for an answer, from any persona. But it was to be Scarecrow, with a teasing smile, who jumped in with the answer. When it comes to the bone, we were rather counting on you knowing Doctor, being you are if not, the oldest, the present model. Sorry, no idea, she said, shaking her head. Apparently all I seem capable of doing of late is fainting. She began to rub the back of her neck. Fancy Pants smiled, nodding his approval of the simple gesture. This persona not only dressed like him, he moved much like him too. Wait a minute, Doctor, she addressed Scarecrow. If this is indeed, my time line. Then why are you here, Doctor? Norm shrugged. Don't know. The Doctor did a quick double take towards Scarecrow. My recorder! Scarecrow grimaced, realizing he was on the horns of a delicate dilemma, trying to remember just which flute was his. Very solemnly she tried a few notes. Removing the flute from her lips, eying Cricket, she began to filk; quickly joined by Scarecrow on his flute: Doctor is Welch man. Doctor is a thief. Doctor came to my TARDIS. Cricket nodded distastefully. Satisfied, she did indeed have her recorder back, she smiled warmly at Scarecrow. Thank you, Doctor. Tucking the recorder in her caped coat she mused. Wonder if any of this, has anything to do with the Cloister Bell? The Cloister Bell! Cricket and Joseph exclaimed as one. Yes, it's been ringing all day. She thought a moment longer. Wait a minute, her eyes narrowed. I collapsed on the deck. I don't remember moving. Guilty, Teeth and Curls interrupted, raising his hand. I moved you to higher ground, so you could breathe easier. The Doctor put a hand to her forehead, running it across the jangle of her curls. Phew! I was beginning to think this regeneration had picked up the nasty habit of sleepwalking! Then, sometime, when the TARDIS slipped into Vortex. The Doctors sniggered. The Doctor looked back at Teeth and Curls. Thank you, Doctor. You're most welcome, ah, Doctor. Teeth and Curls' grin was most suspect. How do you feel now, Doctor? Cricket asked, his voice full of concern. Champion! Spoken like a true batsman, Cricket said nodding. You did stay in the Zero Room long enough, didn't you, Doctor? The what? Standing up, her vacant look swiftly changed to a teasing, lopsided grin. Actually Doctor, I discovered the most unique, that is. Suddenly she was lively animate. I now know why the Zero Room is pink and why it smells like roses! Excitedly she continued. It all came to me while I was playing my recorder. Her eyes narrowed, Or was it while I was doing my sums? Hum, something to do with Diff-E-Q, I'm sure. Scarecrow chuckled. He did the very same thing, whenever he was trying to muddle through a puzzle; play the flute, or mutter mock theta computations. That's all very interesting, Doctor, I'm sure, Teeth and Curls interrupted her, unable to contain himself any longer. But I'm far more curious about. Once again his somewhat, annoying habit of leaving a sentence hanging in the air grabbed a hold of him. Teeth and Curls cleared his throat. When did you first discover the most unusual effect this regeneration had on you. The Doctor smiled widely at Teeth and Curls insight. About fifteen minutes after it happened. It came to me in a flash, in the shower. Teeth and Curls nodded soberly. I see. The other Doctors were puzzled by the dialog passing between the two aspects of self, but at the same time , they too were intensely curious, aware that something was obviously being shared by the two who were one. But I can't begin to understand the how, nor the why of it, Doctor she said softly, beginning to pace. Her eyes fell questioningly on Norm. But if I share everything I know with you, Doctor, where would be the fun? Fun? Fun? Teeth and Curls stroked his chin, grinning compassionately. Turning to the others he smiled. Extending his arm widely, with a flourish, he began. Doctors, this regeneration of us is female. Impossible! Joseph snorted, stepping closer to the two, unwilling to admit, even for a moment, he had been that close to being a female himself. The Doctor shook her small head slowly but determinedly, taking her 500 year diary (volume 2) from a pocket in the Cape. No, it is not an impossibility, the nod said. See, Doctors, it's all here on page one. She began to read from the entry: Dear diary. Woke up today to discover I had regenerated female. Went back to bed. Really! This was not a particularly good day! Will save the Universe. Maybe. I really need to think about this. Love, the Doctor. The Doctor looked towards Joseph, suddenly puzzled. Something the matter, Doctor? Joseph asked tentatively. You've changed, your costume, that is. You approve of the new look? Joseph inquired, slipping off the familiar multi hued top coat, to reveal a cranberry colored velvet suit underneath, and a pair of corespondent cranberry and white wing tip shoes. Everything is beautifully coordinated! Joseph chuckled. Yes, I know. Knickers and matching coat, white shirt and stockings, gold lame tie and waistcoat, she said, taking inventory. Well, I'm impressed. Thank you. Joseph smiled at her, warmly. I have dressed like this since Rassilon has seen to employ us as his inter retinue. Joseph stopped himself, as Number One began to slowly shake his head in disapproval. Then why not whole hog and opera Cape like me and Doctor three? Because I still like the great coat, Madame! Joseph insisted. The Doctor smiled warmly. Me too. Actually, I have one just like it. Joseph's eyebrow arched. And you wear it? She shrugged. Occasionally. It is all right, isn't it? Right as rain, lil Doc, Joseph said, gently chucking her chin. Jumping' Jehosophat! Fancy Pants exclaimed, as if he had only just realized, she was a girl. That's what I said, she replied, shutting her diary with a snap, putting it back in her pocket. Fancy Pants smiled tenderly. Does explain the flutterwing perfume, though. I was beginning to question my nose. But I have always questioned it, Doctor, she said facetiously, chuckling softly. Touche! Fancy Pants conceded. My stars! Scarecrow gasped, this he, who was so much like him, was in reality, a she. I remembering saying that also, the pint sized Doctor said, giggling. No wonder, you're having such a time of it, Number One stuttered. This is far more than just dendrite, neuron, and synapse ambience, my boy. The Doctor, just a bit annoyed, placed her hands on her hips. Do tell. We know, how you knew, Doctor, Cricket said to Norm. But how did you, the fourth persona, know, Doctor? Cricket asked Teeth and Curls. When I first picked her up, Teeth and Curls began. I couldn't help but notice her mass was distributed all wrong, for a chap anyway. He arched a brow. And she was rather, ah, somewhat, he paused. Lumpy. The Doctors laughed. Then, Teeth and Curls continued. Following through with my theory, I listened very close to her voice, while observing her dainty, little hands, and tiny nose. And, as the third persona has already pointed out to us, there was the no small matter of perfume, and. Teeth and Curls paused, giving himself time to carefully lift the jangle of curls that just covered the Doctor's ears. The piece de resistance, Doctors: earrings! Therefore. Grinning and with a flourish of his hand, Teeth and Curls rested his case. Scarecrow snorted. Maybe he's just a pint sized pirate! Who sports a flute instead of carrying round a parrot. The Doctors chuckled. Just as quickly seven of them became very solemn. Realizing just who, and what she was, the Time Lords found themselves, suddenly very protective of this one of a kind self. What's the drill now, Doctor? Scarecrow asked Fancy Pants. Fancy Pants shrugged, rubbing the back of his neck. He had no idea. Cricket tugged at Joseph's sleeve. She'll require special care and support. What with the Estrus and everything else. Joseph's eyebrow arched even higher than usual, and he nodded solemnly in agreement. Without question, Doctor. Fancy Pants stroked his nose,and looked straight at Teeth and Curls. I fear I must agree with, the fifth persona, Doctor. Whatever the present problem on Gallifrey is, we can't allow this Doctor to become embroiled in it. I feel somehow the lives of our other six future selves are already at risk due to. To, ah, her rather unfortunate turn of events. Norm stepped towards Fancy Pants. He cleared his throat. I wouldn't go so far as to say that, old chap, he informed, a trace of disgruntlement in his voice. Norm felt himself. Seems everything turned out, right as nine pins to me. In all the hubbub, the Doctor's face took on a cross between whimsey and irritation. She was being avidly discussed yet totally ignored. What she felt, or wanted, or even needed, seemed to her to be of little consequence to these other selves. If this is The Game of Rassilon the old boy has certainly altered the rules this time, Number One jested. Scarecrow grinned coyly and nodded. But a light came on in Teeth and Curls' intensely blue eyes. Doctor, you may be on to something. I could, I mean, I am? What? Number One asked, taken slightly aback. Teeth and Curls nodded. Maybe Rassilon is the key to all of this. No telling what the wily, old bird might be dabbling in now. He turned his head towards the Doctor. Or what possible, long term effects his tampering could have on us. Then as an after thought added: Or Gallifrey. For the very first time, when their time lines merged, the Doctors were in total agreement. The thing to be done was to seek out Rassilon, and that meant a trip to The Dead Zone. As one the Doctors turned to face this nonpareil self. Fancy Pants was the first to speak. Up to a trip to The Dead Zone, my dear? The Doctor started to nod when suddenly, without warning Fancy Pants silently disappeared! Hard to disappear Sarah, he's just not visible, she muttered to herself. Shall we each take our own TARDIS gentlemen, or are we invited to share in this one, dear Time Lady? Number One asked, stepping up to her to gallantly take her hand. When as before, with no warning, the hand, along with the rest of the first persona, slowly dissolved before the Doctor's eyes. A united front would definitely the advised course of action, Cricket affirmed, apparently, totally unaware of the other Doctors' fates. Don't you agree, Doctor? But before she could respond, Cricket too was gone. I don't care if you are a girl, Scarecrow soothed. You'll always be, a little chap with flute, to me! Indeed! I'll set the controls. These short hops are always so unpredictable, Joseph said smiling. Like some Doctors. When the Doctor smiled back at that, Joseph commenced to fade. Quickly turning her head towards Norm, the Doctor asked. We are a male, once again? As if seeking some reassurance before he too might be whisked away. Norm chuckled. Screaming from every cell. Just as you are the real Mc Coy, Doctor. The Doctor giggled. Norm winked. Rest assured, Doctor, Rassilon has the most. And Norm too was gone. Turning towards Teeth and Curls, the Doctor looked at him quite wistfully. You're the only one left. I suppose you're going to disappear now too? Teeth and Curls flashed her a toothy grin and shrugged, tousling his curls about in total ignorance. This time she wasn't surprised in the least when Teeth and Curls simply winked out, with no more than a wink and a wave. The Doctor so suddenly abandoned shook her head. Alice in Wonderland? Oh Pooh! She has nothing on me. How can you compare one smiling Cheshire now you see him now you don't cat to seven grinning, vaporizing Doctors? Closing her eyes, she sighed deeply. Slowly opening her eyes the Doctor was made very aware of two things. First. Second. The Doctor jerked her caped coat from her face. Now I'm hallucinating! Gruffly grabbing the lapels of her coat she admonished. Doctor, pull yourselves together or it's three more days in the Zero Room for you! Slowly up righting herself, she began to speak to herself once more. Although, she slowly considered. Maybe all that rot, rubbish, and Danny Deever stuff has provided me with a key. Rassilon! Rassilon, the greatest architect, engineer, and thinker in all of Time Lord history. Could Rassilon be the answer? If so. She pouted. Am I on Jeopardy? The Doctor sighed. Or perhaps I should put that in the form of a question. Am I in jeopardy. Scrambling to her feet she looked at the still silent, exterior scanner. But first things first, Doctor. A troop of Chancellery guardsmen were slowly advancing on the TARDIS. In the lead was Commander Maxil. The Doctor smiled, arching a brow. An official escort? She swiped a hand across her face. Why, for a change, they almost seem friendly. She contemplated further. Shall we take a chance, Doctor? Take a little recce? Patting the console she grinned. Well, ol' thing, here goes the seventh persona of Dokk-tor to welcome her very first Gallifreyan sunrise. Turning she headed for The Way Out. At the threshold she squared her shoulders, drawing herself up to her full height. Then she sighed, deeply offended. Like it or not 5' 4 would just have to do. The interior of the CyberShip was scrupulously clean. Everything glistened including the CyberMen. But the ship was totally devoid of any warmth: either physically or emotionally. It was in the Doctor's own turn of phrase: early Mondas sterile. A CyberLieutenant entered and spoke: The Time Lord is here, Leader. Bring him to me, the CyberLeader commanded just as mechanically. As impersonal as a machine the CyberLieutenant saluted, turned and obeyed. In but a few moments the Master, with his all too familiar pas de chat, was escorted, between two CyberMen, into the control room. Greetings, Leader! I bring news that will be of great benefit to us all. It entails a permanent home for your people, and the final solution to a very old and very persistent plague that has on occasion beleaguered the both of us. With his usual flourish the Master bowed graciously, his evil cackle heard plainly over the sounds of CyberMachinery. Standing in the High Council chamber, surrounded by the members of the Inter-Council, the Doctor not so patiently waited as the appointed President Flavia introduced the seventh persona of the one called Doctor to the assembled Time Lords. Inwardly the Doctor was reminded, yet again, of just how stuffy, boring and endless the ceremonies of Gallifrey could be. The outward Doctor however bowed courteously. Whatever the problem on Gallifrey was, the Time Lords would get round to it. Once they got past the formalities that is. Hopefully the Universe could hold on, and hold out, until all was observed. Was it any wonder each trip to Gallifrey only reinforced her belief she had been right so long ago in tampering with the computer at Prydon Academy, changing a one Theta Sigma's exceptionally high grades to an ignominious disaster. Flavia's concluding words brought the Doctor back to the present. I believe everyone else on the Inter-Council is known to you, Doctor. Yes your Excellency, thank you. Castellan Terrace, Councilor Sondergraff, Councilor Matthew, Cardinal Raymond. The Doctor bowed in Gallifreyan obeisance. The Castellan was the last one to return a bow, and it was painfully obvious to all, he was not president of the Doctor's fan club. A shuffling sound caused the Doctor to turn her head towards it. Another Time Lord had just entered the Council chamber. Chief Surgeon General! This was one Gallifreyan she was genuinely delighted to see. Taking his offered hand she shook it zestfully. Dear and glorious physician, how goes Lord Gomer's Cyclic Bursts? Quite well, Doctor, Gomer replied, smiling warmly. Quite well. You never do forget, do you, Lord President? The Doctor slightly embarrassed by the greeting, although it certainly was Gallifreyan proper, shook her head. But that Presidential thing was ages ago, Lord Gomer. She sighed. And only briefly at that. I would have hoped all of Gallifrey had forgotten. Flavia gently touched the Doctor's sleeve interrupting her, as Gomer began to shake his head in respectful disagreement. Doctor, there is another gentle man who is most anxious to see you , as it was he who located your TARDIS for us; and under my order sent out the Panoptican summons. Turning from Gomer, the Doctor beamed, as the old Gallifreyan slowly tottered towards her. Damon! My dear, dear ol' thing! The Doctor placed a heartsfelt hand on Damon's stooped shoulder, shaking his hand animatedly, recognizing his attire instantly. Well. And about time too. Congratulations! Thank you, Doctor, Damon replied softly. Though it is you who are mainly responsible for the. The Doctor sighed, looking deeply into the ancient face of her long time friend. Has it truly been that long since I was last on Gallifrey? Yes, Doctor, the years measure far too well against my face. Soon I too will Regenerate. Then let's fast and pray you have much better luck with yours, than I had with mine. The Doctor's soft hazel eyes sparkled impishly. Damon looked at his friend, uncomprehending. But then, Damon very seldom understood this enigmatic friend. At one time he pondered, that his lack of comprehension came from the fact that unlike the Doctor, Damon was not a Time Lord. But life long experiences had taught the gentle Shebogan, the other Time Lords, even those of the Inter-Council, presently surrounding him, understood this Prydonian renegade no whit better than himself. To Damon, it was enough that the Doctor was simply the Doctor, and his trusted, valued friend. Damon case his eyes downward. Other than a little short of stature. And what, ever so kind and wise, eyes. The Doctor smiled warmly. Thank you, Damon. Damon bowed. And once again you return to us a hero, he said solemnly. The Doctor arched a brow. I do? Well, Damon said with a snort. I certainly do not make it a habit of traveling round the Cosmos in a rackety old TT capsule saving entire civilizations from Daleks at the jeopardy of interrupted life spans! Of course you don't my dear lad, she agreed, placing an arm on his shoulder. You have much more sense than that. But Damon, surely you know how these things arrive from afar greatly exaggerated. The people of Pisces Australis Nine are not ones to embellish formal reports when they informed us of your involvement, which resulted in the termination of Dalek plans on their world, Damon insisted. I saw the public register videos myself. The Doctor, obviously uncomfortable with Damon's enthusiastic praise, interrupted him, hoping to redirect him. Perhaps not Damon, but the people of PA-9 do tend to carry on so. She moved to place a hand on Damon's shoulder for a second time. But always a pleasure to see the one who helped us defeat Omega. Turning from him she faced the most powerful figure on Gallifrey. Clasping her hand together in a gesture highly reminiscent of Cricket, the Doctor inquired. And just why, President Flavia, did Damon summon me to Gallifrey? Flavia pointed a way, leading the Doctor to a room adjoining the Council chamber. She stopped in front of a large computer complex, singling out a specific technician. Doctor, this is Technician Torbis. Greetings, Tesh, the Doctor intoned mystically, her fingertips together, before her face. Tesh, Doctor? Torbis asked. The Doctor smiled, rubbing her nose. Ah yes, long story. Perhaps after all this is settled, not only will I explain this Tesh thing to you, but I will be able to look up three very dear friends. Bending over the technician's shoulder, deeply engrossed in the primary console's read out, she sighed deeply. Andred. Leela. K9. Seems all I have is former friends. The Doctor flipped her Cape out of the way. It was if her action also tossed aside all jest. What seems to be your problem? But already the Doctor had guessed most of the answer, after all, she could read Gallifreyan script very well (it was the only Text the TARDIS knew); as to her knowledge of the MATRIX, well, it was knowledge intimate. Torbis pointed to a spot on the monitor, where a small irregular pulsation was being intensely watch dogged. Well, Doctor? The Doctor's eyes journeyed to the larger visual display of the MATRIX network overhead. Torbis continued. As you can plainly see Doctor, the MARTIX'es neuron field is being drained of its hyperon energies in the frequencies seven through nine. And apparently by something that resembles a C. Her voice tailed away as she realized what she was saying. She began to stroke her cheek. That's not supposed to happen, Monitor. But how can you say that with such certainty, Doctor? Flavia asked. The scientist in Flavia took over. CVE's are random. Not necessarily your Excellency, the Doctor gently corrected her, remembering all too well the scientist mathematicians of Logopolis, creators of the CVE's, as she began keying in queries, commandeering Torbis'es console. The Doctor understood the reasons behind Flavia's misconceptions, but like most science lessons, they would keep, for a safer, quieter time. Her eyes returned to overhead image of the MATRIX. She began to chew her thumbnail, concentrating intently. Flavia smiled. One of the Doctor's earlier persona use to do that whenever he was concentrating. Which one was it? Ah yes, the third one, the fancy dresser; during that unfortunate time of his confinement on Earth. Aup! Traveling back to Torbis'es monitor, her eyes narrowed. There it is again! What, Doctor? No, Who, actually, Dr Who, at your service. Torbis was more confused than ever. This here, man, she indicated, jabbing at a small bullet on his screen that was there, then gone, then back again. If you blink you might miss it. Looks to me like a recursive power surge emanating from , the Doctor paused. The Dead Zone. What do you think, Tesh? The Doctor looked straight at Flavia. The Doctor's words had brought a sudden hush to the huddled members of the Inter-Council. Who has activated The Dead Zone your Excellency? And to what end? I do not know, Doctor, Flavia answered. The recursion was not present until just this now. Was it Technician Torbis? Torbis nodded. Castellan Terrace nodded wordlessly, evincing his own silent protest. The Doctor always meant trouble. The vote to send for him would not have been unanimous had he not been bullied! Now they would all pay, dearly. Aren't things severe enough with the MATRIX in jeopardy? Flavia asked, pulling her white, presidential robe tighter round her. Apparently someone thinks not, Madame President, the Doctor replied. Technician Torbis hadn't taken his eyes off the shiny black bullet that came and went, nor from his monitor through all the previous question and debate. You are correct, Doctor, Torbis informed. The recursion seems to have localized round these coordinates. The Dead Zone? The Doctor sighed deeply, rubbing her face with her left hand. Torbis nodded. Why do I feel I'm being drawn into Rassilon's lair with or without my permission? Torbis shrugged. Well, thanks for all the fish, Tesh, anyway. Turning towards Flavia the Doctor decided. The Inter-Council will have to take the matter under consideration, Doctor, Flavia answered softly. The two anomalies may not be related, Doctor. The Doctor's shoulders drooped. She had hoped just once to circumvent the red tape of one of the oldest bureaucracy in the Cosmos, but could feel herself being tied round and round with it until she couldn't move. So she did the only thing she could do, she surrendered to hopefully older and wiser heads. Of course, Madame President, she said bowing. I will wait in my capsule for your decision, she said, looking round tentatively. If that is satisfactory. But what the Doctor really wanted to say was she was primed and ready for action, and was very impatient with all this blasted Gallifreyan inaction. As you wish, Doctor, Flavia decided. Doctor? Yes, Damon? May I come with you? Of course dear friend, perhaps you would be so good to fill me in on my old companions. Leela and her husband Andred, and, and, of course K9 too. Damon nodded. And Adric? Adric? Damon nodded once again, but this time, very mysteriously. The Doctor stuck out her chin. Adric? Curious as she was, she still remembered her manners. She turned, and bowed to the President a second time. And started to leave then, when a tense smile crept across her slender face. She froze in her tracks. She was being shadowed, by Maxil and his lads. Throwing up her arms, she accepted too, this inevitability. With all the arrogance of a seasoned Prydonian, the Doctor strode across Computer Central, the High Council Chamber, and into the Capitol's maze of corridors; shadowed by the Commander and his troop. The eyes of the Inter-Council ever riveted on the disappearing form of the Doctor, and right behind her, Coordinator Damon. The Master was busily monitoring several terminals in the gaming room of his TARDIS. He appeared very pleased, almost buoyant. Oh, very good, very good, he said, gloating, tapping his index fingers together. The CVE tap in, plus the combined energies from my TARDIS, and the CyberShip, are draining the MARTIX'es hyperon energies most satisfactorily. Who but I would have thought to use my Omegan Crystal Banks as a storage battery? Who indeed? The Master chuckled with glee. But suddenly he was deadly serious Still, how very difficult it is to stand here, observing, and patiently waiting. What the Master was so intently looking at was a slowly rising column of a purple fluid in a transparent tube. But when the level finally does reach the top mark I will be able to reinstate a new cycle of Regenerations for myself. His eyes narrowed. And I will finally be able to even the score with those Time Louses who once promised to give me a second cycle. Rubbing his gloved hands together the Master seemed to make up his mind. Yes, yes. Now, to keep my promise to those tin plated, rusty, sardine cans. Destruction of the Transduction Barrier, and elimination of the Quantum Force Field surrounding Gallifrey. I knew becoming one with the MATRIX would one day come in handy. And I believe that little tune goes something like this. A very menacing gleam came to the Master's cold, dark stare; and he smiled. At present Damon was sitting in a large, high backed, veranda chair, in a large room the Doctor had long ago nicknamed the Bathroom. Actually it was much more than just that. True, it did contain necessary facilities for both male and female companions, but it also housed a large swimming pool, showers, a video game room, patio, theater, and automated buffeteria. It even housed a partridge in a pear tree, from the time when one of Fancy Pant's companions had commented that, that was the only thing that wasn't in the Doctor's Bathroom! The Doctor quietly entered, in the stomach churning attire of her former self. As Damon continued to stare at the Doctor, he knew something was definitely different about him, but just what it was eluded him completely. The Doctor grinned toothily. Much better. But one always feels better after a shower. Now to the hearty meal. Perhaps my last. Please don't talk like that, Doctor. The Doctor bent over, close to Damon. OK. So. Damon was speechless, males of Gallifrey did not wear earrings, pierced or otherwise; not even the outlaw Seedle Warriors! And was that flutterwing perfume attacking his nose? No, he must be mistaken. True, his friend was a renegade, often doing the unexpected, but never was the Doctor caught being anything but male! Sulking in frustration at Damon's silence - couldn't Damon add two and two? Bounding to her feet she hurriedly removed the multi patterned coat, and dark veined waistcoat that looked as if someone may have sicked up on it. What was revealed to Damon then, was a pair of white braces festooned with bright red question marks, a white shirt, and beneath them, a delicately flowing, unmistakably feminine form. Damon was appalled. Doctor! You're a girl! You noticed that, Damon, the Doctor teased. Yes, Damon had noticed, but he was also astounded. But. Some sort of alien virus? The Doctor chuckled, lacing her fingertips together. I don't know! Her eyes narrowed. Though, I doubt that this is an illness I'll quickly recover from. Damon nodded with a sly grin. And you thought Gallifrey had problems. Ha! Returning to the security of the waistcoat and Technicolor coat, the Doctor sat down, beginning to devour her synthetic meal. Damon, rude as he knew it was, just continued to stare. It is quite remarkable just how much you do resemble the Doctor, Doctor, Damon said absently. But I am the Doctor, Damon, the Doctor offered. No, I mean the older one, the fourth one. But my dear Damon, I am older than any of them, the Doctor argued. Any of them to date, anyWHO. Oh dear, Damon sighed. This is all getting very complicated! Getting, Coordinator Damon? Getting? The Doctor looked at her long time friend warily, her soft hazel eyes widening with their whimsey. The CyberLieutenant looked up from his console. Leader, we are nine spans from Gallifrey and closing. By now they must have us on sensor range, and will be placing their planet's security on amber alert. Then let them have the security of their amber alert for all the good it will do them, the CyberLeader said. All is going as planned. Yes, Leader, the CyberLieutenant replied, as devoid of any emotion as his leader had been. The Doctor escorted between Coordinator Damon and President Flavia entered the large cream colored main Console Room of the TARDIS. The Doctor was still shaking her head. I don't know about all this Madame President. Seems equably fair to me, Doctor. You fix, as you say, the drain on the MATRIX, and we will at long last fix your TARDIS. Bartering, she mumbled, head bowed. Looking up to Flavia the Doctor smiled widely, nodding. OK, I agree. Wonderful, Flavia beamed. It was with a great deal of pleasure that Flavia introduced her chosen team of technicians to the Doctor. For once he was, that is, she was going to allow someone to return a favor. Besides, Technician Russeell began, assuring the Doctor, It really is better to leave this kind of troubleshooting to the experts. Whaaa. Experts? Experts! Listen, Tesh, I am not some fly by night. Saturday. The Doctor was ready to do bodily harm. Unknown to him, Russeell had tromped on a very sensitive nerve. Her eyes narrowed. I understand the TARDIS perfectly! There is not one single part of her system that I haven't adjusted, or tweaked, or repaired, or replaced, or bubble gummed together, at sometime or the other. The Doctor began to pace animatedly round the wide console, and the head technician. I even put her back together after a third of her was jettisoned. And replacing a deleted Zero Room is not for part time amateurs! She began to bluster. The hex computations alone took me weeks of work! Sure could have used. Adric. The Doctor drew herself up to her full height. Furthermore, I am in complete and constant control of her! The Doctor winced, and scratched her head. Ah, except for this one, tiny, little, Cloister Bell thing. Without missing a beat she changed the subject. And while you're at it Technician Russeell, would you be so good as to fix my Temporal Grace Circuit? I am really quite at odds without it. Yes, Doctor. The Doctor began to pace once more, seeming very close to changing her mind. Seeing the avalanche of technicians beginning to descend on her main console was, at best, unnerving. So many. Lady President? Flavia nodded. Only the best for a former President. The Doctor snarled. Oh yes, the Doctor suddenly remembered. The audio linkup on the scanner, it's on the friz again. I suppose you don't have a Friz who could fix it up smartly? Russeell shook his head. No, will a Cristoefur do? The Doctor shrugged. I suppose. Always was fond of that name. I remember telling Chris Columbus. Chameleon Circuit! Doctor? After leaving Jaconda, I tinkered with the circuit, and the TARDIS actually changed its appearance three times: to a wardrobe, and a pipe organ, and finally into a set of double doors; before it rubber banded back into the police box. The Doctor's face soured. Will see what I can do. I'll put Keneth on it. He's the best I have with CC circuits. One more thing, Tesh, the Doctor launched off again. Whenever you start taking her apart, several other things will go zap in the interim. Grimacing, her pacing began again. When next she spoke it was like she was lecturing to a class. Now remember, type forties generate a low intensity telepathic field, and that's tied into her primary defense system, and my own isomorphic connection with her, please not to disrupt that. I've already checked out the main logic junctions in the system circuitry board, and everything checked out green. I logged it all in my maintenance log. Oh yes, I did leave the panel undone. Terribly heavy hatch. She stopped her pacing and turned to face the head technician, pausing for breath. Her brief moment of silence gave the grinning Russeell time to respond. Yes, Doctor, will oversee it all, personally. And Doctor? Yes? You are to commended for keeping this TT capsule in such good condition, but. Thank you, the Doctor cut in. It really has never been my forte, you know, she teased. Very quickly her confident look began to slowly fade. Actually, I've never left her to anyone, since I, ah, first borrowed her awhile back. Her face brightened. Come to think of it, she was in for repairs at the time. Had time not been so much at a premium I. She began to smile, leaving the thought hanging, and unspoken. Grinning, Flavia nodded towards Damon. He had told her there was much of the fourth persona in this seventh Doctor. She had to agree. This one had rediscovered his annoying habit, leaving thoughts and things just hanging. This Doctor, obviously, was still deep in the process of finding himself. The Doctor turned towards her console. There. Don't worry, you are in expert hands at long last. She winced, hoping that statement was true. Lowering her voice she continued. We both know, ol' thing, this is long. Not to worry, Doctor, when you return, and we're finished, you'll think you have a fifty six beneath your feet, Russeell said, brimming with confidence. Somewhat nervously the Doctor shook the technician's hand. Then happy hunting, Tesh. Turning towards Damon and Flavia she squared her shoulders. Lady President, Friend Damon, she acknowledged gesturing towards the Exit. Suddenly the Doctor spun round to the technician in charge. Yes, oh yes. One Space/Time element, please. Preferably without a recall circuit! Flavia, Damon, and Tesh-nician Russeell smiled widely. It was quiet in the small examining room. The Doctor (dressed only in a convalescent gown) and President Flavia were waiting for the return of the Surgeon General, Lord Gomer. Dangling her slender legs over the examining table the Doctor began to, out of sheer boredom, swing them slowly back and forth. Your suggestion was an excellent one, Madame President. It will be nice to know exactly what I have become. She pouted. You know, I could be the beginning of a whole new species! Flavia smiled, with motherly affection. I am of the opinion, Doctor, you are but a member of a very old, and very nobel, Gallifreyan one. Flavia's eyes narrowed, her gaze falling on the Doctor's untouched legs. And as soon as we finish your blood work, Gomer is going to permanently remove the hair from your legs. The Doctor scowled. Why? It's my hair! But totally unacceptable to a female of Gallifrey. But don't worry your little head, Doctor, ibbo electrolysis will solve your problem painlessly, once and for all. But I don't have a problem! Besides, if you think I'm going to let Gomer within ten feet of me with his smokin' laser. Please trust me Doctor, Flavia soothed, gently stroking her mass of soft curls. There is much for you to learn about your muliebrity. The Doctor began to mutter to herself. I shouldn't have listened to Damon. I shouldn't have told you. I should have just fixed the MATRIX, and then split. She looked up. Besides, it has not yet been conclusively established that I am woman. She began to grin. Actually, I don't know what I am! Depending upon my glandular composition, and the presence or lack therein of any and all Y chromosomes I could. What about Y chromosomes Gomer interrupted, entering the small examination room, a tray in his hands. Oh, no, no, no,no. You already have enough to float a battleship! Just one more set of samples, Doctor, Gomer promised, wondering, ('what in the green crystals of Omega was a battleship'). The Doctor snarled at him. If you think I am gong to buy into that, Surgeon General. The physician ignored her, proceeding to draw the required blood samples. Well. And female, Gomer chuckled. You're enjoying this Gomer of theCyclic Bursts, aren't you? Actually, Doctor, apart from that, I am far more curious as to why and how you regenerated as you did. From the test results I have already seen, every cell in your body, hair, blood, skin, has been metamorphosed somehow. The expense of energy on the atomic level required to effect such a change. And yet. No Y chromosomes then? Gomer slowly shook his head. How about under the left kidney? Gomer just kept shaking his head. Ah, ha! Hiding beneath the wattles? No! Wrong count, totally wrong color, Gomer insisted. Flavia giggled. The Doctor sighed deeply. So I've counted. Her sad, lost puppy eyes journeyed briefly to Flavia. Well, Gomer, I guess you best get out your ibbo laser, she said with total resignation. Gomer's brow furrowed briefly in puzzlement; but Flavia was smiling warmly in approval. The Doctor stood by the open ended trans matt chamber awaiting transduction, surrounded by President Flavia, the members of the Inter-Council, Commander Maxil and his guard, and a very solemn Coordinator Damon. The governing body had voted to allow only one Gallifreyan access to The Dead Zone, and Damon had so wanted to make that two Gallifreyans. But that was not to be. So now he could but stand and wait. Wait and hope for the Doctor's safe return. The Doctor was dressed this time (for a very sound reason) in a pair of red and white stripped trousers, and a tan Victorian cricket jacket. This recall device will signal us when you wish to return to us, Doctor, Flavia began. The Doctor smiled. I remember, Lady President, she replied taking the small compact size, circular device from Flavia's hand, placing it in her pocket. Thank you. Good luck, Doctor, and do be careful, Flavia requested. Thank you, your Excellency. But aren't I always? Looking skywards towards an imaginary friend, she smiled, replying in a precise Scottish brogue. One to beam up, Mr Scott. Unfortunately the Doctor's final response was lost to Flavia and the others, as the shimmering blur of the trans matt beam converted the Time Lady to electrical impulses. The wind was howling, and it stung her face as the Doctor materialized in The Dead Zone. Not exactly bull's eye, Flavia, she said, looking round grimly at the craggy, desolate landscape. Off in the distance, over extremely rough terrain, stood Rassilon's Tower. The Doctor shook her head. Looks like I'm in for a bit of a hike. All of a sudden she knew she was going to be violently sick. Oh great, this is all I need, she griped. I'm really beginning to hate this body. As Technician Torbis continued to monitor his console, all at once things began to happen. And very quickly at that. President Flavia! Torbis exclaimed, his eyes never leaving his monitor. Flavia silently glided to his side. Yes, Technician Torbis? Lady President, the energy levels are stabilizing. In the MATRIX or in The Dead Zone? Torbis'es eyes remained glued to the console before him as his voice's volume rose. I don't believe this. Now, he paused briefly. Now they're diminishing! In total disbelief he stammered. Pre. President Flavia, the recursive power surges have ceased! He whispered to himself. How? How, Doctor? Finally, he allowed his eyes to journey to his sovereign. How, your Excellency? How did the Doctor manage everything so quickly? Flavia was obviously very well pleased, and relieved, but hardly surprised. She turned from Torbis, and slowly walked towards the small, huddled group of fellow Time Lords, speaking softly to herself. Well done, Renegadrix. Reaching the group Flavia began to speak in a normal voice. Very well done, Doctor. Your regeneration has not altered either your adaptability, nor your resourcefulness. Singling out Gomer, she smiled expansively. Do you not concur, Surgeon General? Gomer smiled and nodded, handing her a rather thick, clipped brochure, keeping an identical one for himself. Thank you, Lord Gomer, Flavia acknowledged, taking the copy from him. Mine to keep, of the Doctor's final medical report? Gomer nodded secretively, glancing briefly at Coordinator Damon. Yes, Madame President. Almost as an after thought he added: And medical instructions. Flavia grinned, guardedly. Ah yes, Gomer. Very special instructions for a very special patient. Turning to the others, Flavia was once more her formal self. I realize, some of the Inter-Council were skeptical of the Doctor's involvement in this affair, she said, her eyes falling onto Gomer's Report. However, I think all must agree that the Doctor is quite remarkable, even for a Prydonian. Wouldn't you agree, Lord Gomer? I would even qualify that by saying, your Supremacy, the Doctor is among all of Gallifrey, unique. He turned his gaze towards Damon. Do you think I am being over dramatic with that judgement, Coordinator Damon? Damon smiled, and slowly shook his head. The others in the room began to buzz among themselves. What was the President and the Surgeon General, trying to say to them, that was so apparent, even to a lowly coordinator? What point were they all missing? Now. If only the Doctor can deal with the MATRIX as easily as she has dealt with The Zone. Grinning widely, Gomer and Damon nodded in agreement. One of Gallifrey's best kept secrets had just gone. Groggily. Taking a handkerchief from her pocket she wiped her mouth, wishing she could get a drink of water from somewhere, anywhere, to rid her mouth of its acrid taste. Can't even trans matt without being hurled into the clutches of nausea, vertigo, and projectile vomiting, she said, shaking her head. Oh, last time I felt that crummy, was. She chuckled. Well. She looked round at the unfriendly landscape she now found herself in. Suddenly, a gust of wind whipped round, knocking her Panama hat from her. Bending to retrieve it, before it blew away, she mumbled. Last time I go out without my scarf! Turning slowly round she tried to orientate herself. Ah, there is was! Rassilon's Tower! And only about a half a mile ahead of her, though across some very uneven ground. Well, Doctor, could be worse. One foot in front of the other and we shall soon be able to chat with. Rassilon. With that much decided, the Doctor set off, whistling to herself the USA's WAC official marching song, Duty. Standing alone in the Great Hall of Rassilon's tomb the Doctor let out a deep sigh, as she surveyed the large chamber. OK. What now, Doctor? Deciding, silently, almost reverently the Doctor turned and approached closer the stone bier where Rassilon lay in eternal slumber. You who? Anybody about? Ollie, ollie oxen. A large, optical MASER generated, holographic projection of the head of Rassilon appeared above her and the stilled, corporeal body of the ancient Time Lord; and a disembodied voice boomed out. This is The Game of Rassilon! The Doctor was very uncomfortable, trying to riddle out just exactly what she wanted to say to the venerated Gallifreyan, her hands stuffed deep into her trouser pockets. But she was game to try. Clearing her throat, she removed her hat, and rolling it up, crammed it into the nearest pocket. I know I have regenerated twice since I was last here Lord Rassilon, but I am the one called, Doctor. Perhaps you may recognize me from my costume? Always a pleasure, Renegade, the giant lips responded with a nod from the giant head. The Doctor flashed her toothy grin, grateful no further explanation was necessary. Beautiful, Renegade, the hologram added. Your smile was worth all the effort expended in getting it there. As to you eyes, no other Doctor you have ever been, nor will ever be, has eyes as deep, nor intense, nor as expressive, nor as lovely to look at, as are yours, my dear. The Doctor, not use to being spoken to in such a manner, didn't know exactly how to respond. Until it dawned on her. It was you! Her eyes narrowed. You interfered with my regeneration! The huge, translucent face began to glow with delight. Of course, Renegade. Though I prefer to consider it fruitful experimentation. The Doctor glared distrustfully. This is The Game of Rassilon! Perhaps so Lord Rassilon, but it is all too real a thing to me! You. The projection scowled. No right? You dare to accuse me of wrong doing? Tread lightly Doctor. You are here, you exist Time Lord, because I willed it to be. You stand before me as you are now because I willed that also! The Doctor swallowed hard, her hands moving to cover her face. But why, Lord Rassilon? Am I being punished? The projection's eyes widened. I was not aware, Doctor, being female was a punishment. For what purpose then? Knowledge, and a requiring of your most unique talents. What unique talents? Lowing her hands, the Doctor's tiny nose crinkled with her questions. Sparking eyes? Give them time, Doctor, for they will be made apparent to you. If you are truly unaware of them at present. But, couldn't you have gotten my attention without getting so physical about it? The voice laughed heartily. This experience will be most profitable to you, Doctor. So try to relax. Enjoy this regeneration's many benefits. Benefits? Easy for him to say. No one ever changed his fiery red wattles to pale pink ones. Then a thought occurred to her. Clearing her throat she would try an old and useful tool. If I should be able to help you, Lord Rassilon, with this MATRIX problem, will you reinstate me as I was? The hologram shimmered. This is the Game of Rassilon! I strike no bargains with Time Lords, Time Lord! All at once the features of the projection softened. So too the voice. The Doctor you were is no more. You made that decision to interfere in the crisis on Pisces Australis Nine yourself. I was in no way responsible for his death. I was just involved in this regeneration turning out as it did. And. Only the fluid time between regenerations allowed it to be in the first place. Your metamorphosis evolved from the deepest part of your inside to the outward. The Doctor sighed deeply. That's what Gomer told me. Every cell. Every cell, Renegade, the projection echoed. It would have been impossible to do it any other way. The Doctor chuckled, stroking her cheek. Gomer said it was just impossible. Still, up to this moment, and especially for the last ten days, the Doctor had felt, even if it was just hovering in the back of her mind, that her condition was somehow just temporary. But now, gazing down at the marble floor beneath her, she knew this was how it would be. Left handed and female. The Doctor tried to remember if she had ever felt more frustration. She felt like crying, screaming, running from the room, kicking hallowed walls, anything. But then, with logic of the situation. Looking across to Rassilon's sleeping form she sighed. The words spoken to him then were soft, subdued, tumbling from her mouth like smooth velvet. Permanent? So that is how it is? The hologram nodded. The Doctor sighed deeply, shaking her mass of curls. I do wish you'd thought to read volume two, reverse spells, before you thought to go and zap me, Lord Rassilon she grumbled. The Doctor's natural resiliency was already beginning to surface, and it was to no surprise that even Rassilon responded warmly to it. Well put, Doctor, it said nodding. Very aptly put. Oh! The Doctor suddenly remembered. Lord Rassilon, the reason I was allowed to come to this forbidden place, in the first place. Rassilon's projection smiled secretively. The Master waltzed into his gaming room, smiling to himself. He sat down at one of the terminals, flipping back his Cape with a flourish. Now, to the destruction of the Transduction Barrier, he said, thoughtfully, stroking the Klingonize type beard. Oh, I know I can't dismantle it completely, but just a few holes in it, to allow my silver cohorts access to the surface. The Master began then to zestfully punch in a complex string of numbers. The Security Control Room, on Level 3-0, beneath the Panoptican, in the center of the Capitol, planet of Gallifrey was unattended. That is, no flesh and blood sentry stood there on guard. If there had been, what they would have heard at present would have been the control panels governing the Transduction Barrier making grating, groaning sounds. Next, they would have seen the room fill with smoke. But, as no human sentry was there, all the occurrences passed unseen, but certainly not undetected. The auto guard, sensing all the occurrences, set off a loud alarm. As the smoke screen thickened with dark, ominous swirling, Gallifrey was informed of yet another problem. The Doctor made for a tiny, solitary figure standing alone in front of Rassilon's bier. But having decided things were wholly beyond her reach, or her control, she meekly accepted what was to come. What is it, Lord Rassilon, that you ask of me, then? Rassilon's projection nodded, well pleased. Behold! A small, very bright ball of energy appeared before the Doctor and began to gently bounce and bob at the level of her eyes. Her eyebrows peaked. We're going to have a sing a long? Rassilon ignored the comment. Follow, the voice instructed, with a low, gravelly growl. Barely containing a smile, the Doctor obeyed, and was led by the bouncing ball of energy, through a series of maze like passageways, to a huge, darkened chamber. Deciding she had arrived safely, her face took on a wry smile. Oohs, just like Dungeons and Dragons! As the Doctor was slowly surrounded by house lights, the little ball of energy, serving its purpose, winked out. Grinning, the Doctor wiggled the fingers of her left hand in a wistful gesture of goodbye. Wandering round the room, it appeared the little globe had led her to some sort of gallery, or maybe one might call it a library. Whatever, it truly was a miscellaneous collection. This and that from here and there. The Doctor idly strolled about, carefully picking up first one artefact, and then another. Gazing first at this painting, and then at that statue. I've always felt so at home in museums, she said to no one in particular. She briefly cogitated, rather ruefully; ('probably because I'm usually older than anything else on display'). Quite suddenly she froze, as she at last realized, the Doctor was standing in the middle of nothing less than a Doctor Who collection, literally. For a long while she stood in total silence, and gawked. Finally, forcing her mouth closed, with her hand, the Doctor swallowed. Snorting! Why thank you, Doctor, a disembodied voice rang out. Rassilon's hologram was beaming. The Doctor looked up, only a bit startled to see that the projection had materialized above her. A museum of my lives and travels, Lord Rassilon? Yes! The hologram nodded proudly at his unequaled collection. You do me great honor, Lord Rassilon. The Doctor bowed formally. Continuing her survey of the chamber all at once her curiosity got the best of her. What's in the crates, Lord Rassilon, extra companions. The containers in question were fourteen, large, rectangular cubicles, each standing next to the other, lining one wall of the chamber. The voice chuckled. One might say they were spare parts, Renegade. But do go and see for yourself, it invited. Grinning the Doctor nodded. She stepped up to the first darkened box and waited. The chamber's light slowly turned itself up until it clearly revealed a waxed figure very familiar to the Doctor. It's me! Turning towards Rassilon's projection the Doctor became more formal. I mean, the original, so to speak. Theta Sigma; the Prydonian Renegade; Susan's grandfather; Dr Who the first! But hardly the last. The beginning of a legend, she continued to expound to Rassilon's delight. Or, should I say, the legend begins here? Rassilon's image chuckled at her antics. Gosh he looks so young, she observed. The first persona at his prime, Doctor, the projection elucidated. She nodded. Made sense to her. Turning back towards the distinguished looking gentleman with silver cane and immaculate Victorian suit she asked: Remember Totters Lane and the junk yard, Doctor? That's where we first had the Chameleon Circuit breakdown. Unconsciously she placed her fingers together in the same manner as the first persona use to do, so many, many years ago. We've come a long way since then, haven't we, Number One? Gazing at the waxed figure the Doctor tilted her head back, her right hand stroking her cheek in the gesture so familiar to Susan, and Ian, and Barbara, and so many others. So many thing to remember, Doctor, so many times, so many people. Marco Polo. Daleks. Aztecs. CyberMen. Atlantis. Lightly she tapped the tip of the figure's walking stick. Doctor. Turning from the first cubicle, the Doctor noticed, from the corner of her eye, the light slowly fade, plunging the waxed figure into darkness, once again. Standing akimbo before the second container the Doctor's eyes twinkled in merriment. Hullo, Doctor! How's Jamie and Victoria? Have any more run ins with that horrid woman, Zodin? She smiled at the little man with straight black hair, wearing the long black coat and blue checked trousers that had been rescued from a rubbish bin. What magic was there, in this past Doctor, The Celestial Hobo, that made the present Doctor want to wrap him in cotton wool, and take him home, to cuddle later? But she didn't tarry long with that notion. For there were; things to do, people to see, places to go; the Doctor's mind raced on like quicksilver. What treasures can be found in those oversized pockets, Doctor? The Doctor opened a front coat pocket and peered inside. Yum, jelly babies! Just the thing to remove a sour taste from one's mouth. Quickly retrieving a candy sweet from its wrinkled white sack, it was just as quickly consumed. Returning the folded sack to the pocket, her inventory continued. Alarm clock, snorkel, vacuum flask, one, no, two sonic screwdrivers, catapult, box of Lucifers. She turned to the other pocket. Ball of string, safety pins, two headed Martian coin. The Doctor snickered loudly at that discovery. Scarecrow, will you never learn? A puzzled look swept across her face. This Doctor too, at his prime, Doctor, Rassilon's projection said. Yes, Lord Rassilon, that is not the dilemma. Returning to the second Doctor's, first pocket, she deftly removed the pair of sonic screwdrivers. Turning to the projection she admonished. Tsk, tsk, tsk, Lord Rassilon; wrong, wrong, one per Doctor is all that is necessary. Replacing one of the sonic screwdrivers in Scarecrow's pocket, she pocketed the remaining one. Rediscovering another pocket, with a look of sheer delight the Doctor withdrew the second Doctor's wooden recorder. You know, Number Two, I have a recorder just like this. Removing her own flute from her coat pocket she held them up in front of the unblinking, unseeing waxed figure. How about a duet, ol' thing? The Doctor sighed, making a distinct popping sound with her lips. Another time, another place, perhaps. Solemnly she replaced his flute, and then her own. Take care, pockets of patch, you precious, little man, she said in farewell. Turning, she sighed deeply, then after a brief pause, sauntered slowly to cubicle three. Was it imagination, Rassilon thought, his eyes narrowing, or didn't the second Doctor, on occasion, shuffle about much like that? As the light came on in the third cubicle the Doctor turned towards the image of the immortal Time Lord. You know, Lord Rassilon, your containers look rather disturbingly like coffins. Doesn't that bother you? No, Doctor, not a bit of it. Turning, she mumbled to herself. Well, it for sure bothers me! Looking up she smiled. Well, hullo Fancy Pants. The Doctor turned towards the hologram arching a brow. Immaculate Trousers is the one with all the beautiful assistants. Yes, I am aware, Renegade, the projection said, nodding, barely containing a chuckle. Well, in those dapper days, of long ago, the Doctor said, lightly tapping her fingertips together, I certainly did have a definite mystic. All a once she spun on her heels. How's Betsy? Nee WHO-1. Taking out the extra multipurpose tool, she checked out the cranberry velvet attired Doctor for his sonic screwdriver. Locating it, she shrugged and replaced his, then pocketed the supernumerary. Fetching wrap old fellow, she said, flipping a corner of the third Doctor's Cape aside. Reminds me of a mother hen caring for her brood; keeping them ever protected, under her wings and close to her heart. Ah, hearts! Long live the dapper Gallifreyan peacock, ever sheltering his flock of questing earthmen. Then in unconscious mimic of Rassilon she added. I approve. Strolling the short distance to cubicle four the Doctor positioned herself squarely in front of it. But its light failed to turn itself on. Placing a hand to her mouth she noisily cleared her throat. Still no light. Obviously a product of earth science! Slapping the side of the box gruffly with her opened hand, the light flickered on. Definitely earth technology! Slowly she lifted her head. Well, good morning Teeth and Curls; first draft. The Doctor smiled widely. Then began to rummage through the fourth Doctor's pockets, trying to locate a sonic screwdriver. Finding none, she took from her pocket the extra, and placed it in his pocket. Turning to Rassilon's projection she nodded in conquest. Handy little gadget and no self respecting Doctor should ever be without one. Rassilon's projection nodded its approval. Studying the blue suited, waxed Doctor a moment longer she adjusted the long scarf, done entirely in shades of blue. Take care, user of K9's, she advised, and in perfect imitation of him added: Ha, ha! Then, cocking her head, the Doctor looked at the waxed figure, recalling something that even now she hadn't come totally to terms with. Shaking her head she voluntarily broke the spell. Bending over at the waist, she looked back at the fourth Doctor and smiled. Ha, ha, ha, ha, ha, ha, she mimicked. Her tone suddenly mellowed. Waffle, waffle, waffle. Standing, the Doctor's compassionate smile continued to cascade round this massive former self. Turning her head slightly she smiled wryly. You know, Lord Rassilon, never realized till now, I was such a rippingly large chap. The projection smiled coyly. Perhaps, Doctor, it is because of the stark comparison of what was and is. Indeed! All of eleven inches, just in height alone. Turning, the Doctor lightly bounded over to cubicle number five. As the light spilled its lumens over the silent unmoving form of the fifth Doctor she reached into his pocket and brought forth a cricket ball, which she began to toss in the air. Hullo, Cricket. Spiffy duds old man, she replied, dusting off her own matching coat. Remember when we were here last? We helped Borusa find his immortality. You remember Borusa, don't you, Doctor? Teacher. Cardinal. Chancellor. Lord President. She sighed deeply. Speaking of regenerations, we almost didn't make it through that one, didn't we? Realizing something else she interjected: Hey! It was you Who, who unraveled my scarf! Callous wretch! She giggled gleefully. Well. The Doctor swished the cricket ball across the fifth Doctor's face. Beware of the Caves of Androzani! Then, bowed her head, for a moment of respectful silence. Shrugging, the Doctor re pocketed the cricket ball, and but four large strides put her in front of the next wooden tetragon. Why, hello, Joseph, she addressed the six Doctor, with a wide grin. I notice you're still sporting that coat of many colors. So, how's the physical fitness advocate working out? Or, maybe I should ask, is Mel still insisting you work out? Stepping closer she lightly touched his chest, looking round keenly. Unable to contain the smile playing impishly at the corners of her mouth, she softly confided to him. Did I ever tell you, Doctor, you look strikingly like Commander Maxil? She pouted. Or is it, that Commander Maxil resembles my sixth persona? It all started, I'm sure, with that unfortunate situation with Omega. Like flicking a switch she was serious. Sorry about the Daleks, old man. Not my idea of a picnic either. But very grateful, the Doctor was, Peri, Melanie, or half a dozen other companions she could name were not involved in the skirmish on Pisces Australis Nine. The loss of the living machine Kameleon II had been great enough a burden for the Doctor to bear. Turning and rising up on tip toe, the Doctor attempted to sneak up on cubicle seven. But the sensors were well aware of her movements and brightly lit the interior of coffin number seven. Obviously, not very good second story material, Doctor, she informed. Looking up into identical eyes, she offered a warm greeting. Hullo, me! The wax model was without question a replica of the present Doctor, though it was clad in a navy colored outfit like the one worn by Teeth and Curls of the fourth cubicle. With one obvious exception, the costume was many sizes smaller. Grabbing the waxed Doctor's coat lapels she reconsidered. Or perhaps I should say, hullo Teeth and Curls; revised edition! Ever patiently she adjusted the long multi blue hued scarf of her twin. Then stepping back, she looked over her adjustments. Well, all things considered, guess it could have turned out worse, she said with a grimace. Somehow. Still the scarf wasn't right. Once again she readjusted. One must be very careful, Lord Rassilon, as to how one wears a ten foot scarf. Any, even a slight, miscalculation could result in a broken collar bone, at the very least. The hologram nodded soberly. Turning from 'her' cubicle, the Doctor hopped to box number eight. Hey! You were in my dream! The Doctor slowly turned to the projection. And so were you, manipulating it, she accused, narrowing her eyes, pointing her finger. The image chuckled, nodding. Your stabilization proved difficult even for me, Doctor. Good! But Doctor, I sought to help you whenever and however I could. Yeah! Sure! The Doctor ever curious, removed the tan, Panama hat with its red print hat band, revealing a shock of tousled very dark brown hair, short enough to reveal the eight persona's ears! That's certainly different, she had to confess. Her eyes ever on the move, continued downwards. Norm was wearing a gold colored, golf sweater decorated with red question marks and pea green trim. The Doctor fell instantly in love with it. Rassilon's projection winced, knowing that Norm would not be the only Doctor to wear the offending pullover. Norm's crisp, white shirt was casually unbuttoned at the collar, though he wore a silk, red, paisley tie, with a formal, four in hand knot. Over all was a beige street length coat with the more normal, five foot, brown paisley scarf. Clasp tightly in one hand was a gentleman's black umbrella with a red question mark handle. The Doctor beamed at that discovery. Like the handle, hate the umbrella. Her downward scrutiny continued. Plaid trousers, cross barred pattern, and a pair of corespondents, (brown and white wingtip shoes) completed Norm's outfit. The Doctor scowled. He isn't very large, she sighed. Something amiss, Renegade? Rassilon's projection asked curiously. No, Lord Rassilon, I did ask you for a preview. That you did, Doctor. At least, she sighed, he is male. Most assuredly, Doctor. And he loves to play the spoons. The Doctor looked round. Something was annoying her, and it wasn't because her future self had a perchance for spoon playing. Putting the hat back on the eight persona's head, she whispered to him: Good luck. Placing a hand on his chest, she smiled. Live long and prosper, ol' chap. She though a moment in silence. Just not too soon, ol' bean. Lifting her head, she grinned. Certainly approve of your shoes. Turning from the eight waxed Doctor, the Doctor's brow furrowed. Thirteen personae, but fourteen cubicles, she muttered to herself. She looked to Rassilon's projection. So why, Lord Rassilon. She pointed her finger, hurriedly counting the boxes once more. Fourteen cubicles? I get a break, because this one's female? Another day, Renegade, Rassilon's hologram assured. The Doctor shrugged, accepting, taking in the room once more. OK, then what we have here is: thirteen waxed Doctors, one model K9, a type forty TT capsule, a copy cat, copy cat Kamelion, and miscellaneous and other sundry items of my lives and travels, and one rectangular mystery. Another question then, Lord Rassilon? Could you possibly be the Doctor if you didn't have a question about to escape from your lips? How else can I learn? Then I give you permission to learn, Renegade. Are any of the other, future selves female? Didn't Norm put that question to rest, Doctor? She shook her head. Not entirely. As you so aptly put it, my dear, you are the odd man out, beside being the nexus point. The Doctor looked at Rassilon's hologram puzzled. Six Doctors past, and six more personae yet to come, and everyone of them. Valeyard. The Doctor nodded, well pleased. Clearing her throat she smiled. I'm glad to get that much settled at least, Lord Rassilon. She squared her shoulders. Surely I can endure, what I can not cure, for the length of one regeneration. What's a regeneration? She shrugged. No more than several hundred years at most? Why, I could do that standing on my head, she said with a smirk. Holding my breath! With one hand tied behind my back! The Doctor had really gotten into the swing of things. All of that could be arranged, Doctor, Rassilon's projection said solemnly, nodding. The Doctor slowly looked up at the three dimensional transparency. Something in Rassilon's voice had just sent an icy shiver down her spine. She swallowed hard. By the click/clack Sash of Rassilon! The Doctor braced herself for what was soon, very soon, to come. When the Doctors reappeared they were in an elegantly decorated bed chamber. Asleep atop the massive gold and white trimmed bed of Rassilon, in the white ceremonial robe of the President of Gallifrey was the little Doctor. On her head was an exact copy of the Coronet of Rassilon, and on her left index finger a ring identical to the ruby Ring of Rassilon. Asleep too, but propped against the ornate, to the point of garish, Throne of Rassilon, also in the robe of the President of Gallifrey was the Doctor. Round his neck the gold Sash of Rassilon, and laying in his lap the ebony Rod of Rassilon. On his head was the Matrix Crown of Rassilon. Clasped tightly in his right hand the Great Key of Rassilon. And in no way unexpectedly, the Doctor was not a far distance from the silver Harp of. Rassilon. Batting at a fly he began to stir. No, no, K9, lateral thinking. Van De Cort's theory was wrong, wrong, wrong! The Dynamics of the celestial interfacing is not trilateral. Clearing his throat, he muttered: Now. He looked round warily. Ah, actually; where am I? The Doctor held up the silver trimmed wand. Hey! Who died and made me President. His eyes narrowed. And I for sure have no earthly use for this Key! Looking across the room he saw his reflection in the Great Mirror of (do I need say? Standing he bowed humbly. Up righting himself he narrowed his eyes. OK, what's going on? Looking round further he noticed someone else. The little Doctor snoring softly, on the Bed of Rassilon. Doctor? Approaching closer he reverently sat the Rod, and the Great Key safely on the foot of the bed. Taking off the Crown and the Sash, the Doctor silently placed them beside the other relics. Sitting down on the bed, he began to gently shake the little Doctor awake. Doctor? You. Who. Doctor Whom? The little Doctor's eyes fluttered open, and a very sheepish grin crept onto her slender face, as if she'd just gotten caught with her hand in someone else's cookie jar. She shrugged. Guess, I just popped off again, Doctor, sorry. She began to yawn. Good morning, Doctor. Realizing, she bolted upright. Doctor! The Doctor laughed, then replied very solemnly. Good morning, Madame President. A most chagrinned look swept across her face. Don't be ridiculous, Doctor. We was the President. Like in was. Past tense. Noticing they were both dressed in coronation white, she arched a brow and softly Ooh, ed in wonder. The Doctor grinned toothily. Now she was sounding like K9. Her eyes went to the over large ring on her finger, as her hand went to the heavy coronet on her head. Hey! What's going on? What am I doing with these things? The Doctor smiled widely. Don't know, both times. The little Doctor cocked her head like a puzzled puppy. Guess we are still playing The Game of Rassilon. The Doctor nodded a solemn, ('could be'). The little Doctor smiled warmly. Wait a minute, she quickly reconsidered. I was dead! No, Doctor, he gently corrected, I was the one who was dead. He pouted. Actually, I was dead first. Oh dear, she said lightly touching his chest, you don't suppose we are both dead, do you Doctor? Yuk! What a horribly ugly thought! The little Doctor's face broke out into its wide toothy grin. Well, I suppose Time will tell, Lord of Time. The Doctor nodded. Still. The little Doctor agreed zestfully. Indeed! Warm smiles were exchanged. A sudden gust of wind opened the double doors of the large bed chamber, causing the Doctors to wince. Turning as one towards the sound, the two saw Rassilon slowly glide in! Close behind him, in very humble obeisance was. Good morning, Doctors, Rassilon said simply. The Doctor gawked, in undignified silence. Finally, shaking his head, gathering enough wits about him, the Doctor rose to his feet. So too the little Doctor. The Doctor took the little Doctor's hand into his, and slowly the two walked towards the duo, and bowed. Overcoming their silence, they spoke as one. Good morning, Lord Rassilon. Rassilon laughed heartily. I see both Doctors require an explanation. I will field your questions to me one at a time, Renegades. To say the Doctors were in a state of shock, by several things, actually, would be drawing it mild. Yet, the most nerve shattering of all was the Master's wholly changed attitude as he fluttered about Rassilon, selflessly ministering to him. The Master help seat Rassilon on his throne, and then continued to putter about him. The Doctors followed his silent movements bugeyed, and mouths very undignified agape. The Master, apparently oblivious to all, but Rassilon, bent down and replaced Rassilon's shoes with a pair of sumptuously embroidered Oriental slippers. Finishing that task, he stood and moved to a gold encrusted basin, proceeding to thoroughly wash his hands. The next service was to bring Rassilon a crystal goblet of wine. Sipping the crimson liquid, that refused to stay crimson and was slowly changing in turn in to a whole rainbow of colors, Rassilon began. I did not intercede in the attack on you Doctor, initially, as I was, if you will forgive me, quite busy elsewhere. His eyes journeyed briefly to the Master, who was busy preparing a tray of sweets. And it did happen so very fast. Even for me. The Doctor nodded, conceding. Rassilon's eyes turned towards the little Doctor. But when you, Doctor, volunteered to serve me in gladness eternal in exchange for the Doctor's restoration. I had to accept. The Doctor looked downwards towards the companion at his side. The little Doctor scuffed her white booted foot, shrugging as if to say, ('wasn't nuttin', Doc'). But then, Rassilon continued, looking at the Doctor. When you made me the same offer Doctor, I felt compelled to free the seventh persona, not wishing to see her bound as Foster-Eternal to one as old as I. The Doctor shuffled his larger booted foot, shrugging, as if to say, (twern't nuttin', lil' Doc). Rassilon smiled coyly. Though I did find the idea of a Forever Foster somewhat intriguing. Beginning to blush, the little Doctor lowered her eyes. Thank you, Lord Rassilon, she said softly. Rassilon acknowledged her reply with a nod. But then I decided. Why sacrifice either of you. I like that plan, very good plan, the little Doctor quickly chimed in, whispering to the Doctor. Oh, I agree, Doctor. Very good plan, the Doctor agreed zestfully. My eternal devoted Servitor, the Master, Rassilon concluded. But how much of his past does he recall, Lord Rassilon? Why. Otherwise there could be no element of punishment involved. Then how does he function as he does? Through a lack of his free will and my will forced upon him. But how is it that you are no longer just a projection, Lord Rassilon? And who resides on Rassilon's bier? Rassilon chuckled heartily. I will answer those questions in reverse order, Doctors. First. Rassilon. Second. Rassilon's eyebrow arched quizzically. Sufficient? The little Doctor gestured with both hands opened upwards, as if to say, ('fine with me'). Then crossing her arms she sighed, much like Cricket (persona #5), as if to say then, ('like I really have a choice in the matter'). But enough of me. How are the two of you? As the two considered just how they might answer, the Master brought Rassilon a tray piled high with sweets. Rassilon helped himself to a very generous portion of the goodies. Oh, Doctors, Rassilon realized between mouthfuls. You no longer wear the Inheritance of Rassilon. But, Lord Rassilon, the Doctor argued. Gallifrey already has a very willing President. And a very capable one too, the little Doctor jumped in. Doctor. Doctor. Yes, ah, Master, the Doctor accepted, removing a mint chocolate chip cookie from the crystal serving platter. Doctor, the Master invited, encouraging the little Doctor. Ah. Us Time Ladies have to watch the weight, you know. The Master laughed kindly. Of course, my child. With a polite nod the Master returned to Rassilon's side. Oh Doctor, the little Doctor whispered, gently touching the Doctor's sleeve. The Master is so much like Tremas now. The Doctor nodded. Yes, I know, he said very quietly. Do you think there is a chance. The Doctor's shaking head interrupted her. Don't see how, Doctor. After all this time. I'm quite sure Tremas died as soon as the Master touched him. Some form of electrocution I would suppose, stolen from the Source. The little Doctor nodded slowly in agreement. Rassilon loudly cleared his throat, interrupting the two's thoughts, and conversation. No way did he enjoy not being the center of attention. The current President is not the President of my choosing! We are honored Lord Rassilon, that you would wish. Time Lord! You were born to this end! So I've been told, he chuckled. Several times. But still the Renegade? I guess I'm just having too much fun. Should it ever cease to be. But for the foreseeable future. He then turned to the little Doctor. Then what about you, my child? You noticed, he asked me second? The Doctor tugged on his ear. I noticed that, he said snuffling. Not a bit of it, Doctor, Rassilon sought to soothe ruffled feathers of the female. My first choice, for a long while now has been, the Doctor. The entire Inheritance of Rassilon is his, or hers, for the taking. Rassilon looked straight at the little Doctor. Are you not the Doctor. Doctor? Well. I can write my own hand. Rassilon nodded. Her expression sobered. But as it was you who turned me into a female of our species, you also know why I can't serve Gallifrey as an elected sovereign. Ah yes, Doctor, this non direct access to even your own Autron energy. Are you females never to get it right? The little Doctor shrugged. Something amiss in our genetic engineering, I suppose. A bit lower than Time Lords. Chauvinist, she griped right back. Utterly. Rassilon sighed. But as you are a female, you like President Flavia, would have to be appointed. But I can do that! So. She pouted. I thought I already was. In my own inimitable way. She lifted her chin proudly. Still. She shook her head soundly. But I like it OUT THRERE! But you could like it here, if you would but give it a try, Doctor! The little Doctor curled her upper lip. Did try it. She shook her head soundly. Didn't like it, she replied, sounding very much like Scarecrow (persona #2). Didn't give it time, Rassilon insisted. The little Doctor pressed her lips tightly together. Even though she knew she stood at a crucial crossroad in her lives, it would be unfair to keep Rassilon waiting, indefinitely. The little Doctor's eyes journeyed to the Doctor. Surely the Doctor's wide, pale eyes, as express as they were, would tell her something. Help her out. The Doctor stood silent and unmoving. As for his eyes, they were filled with the cool, detachment of the Prydonian. Borusa would be proud! So. The little Doctor's shoulders drooped. What a pal! But she also knew in the heart of her hearts the Doctor was right. This was to be her decision and hers alone. So in a nanosecond it was. The little Doctor smiled warmly at the Doctor for just the briefest of a moment, then turned to Rassilon. The Doctor has gotten use to a shadow, Lord Rassilon, she began softly. I'd like to think he'd miss me if I were to suddenly disappear. Beyond a shadow of a doubt, the Doctor concurred, his eyes once again their normal, lively animate. It was then that Rassilon realized, once again he and Gallifrey had lost. The Universe with its subtle attraction had claimed victory yet again. The only thing left to him now was to save face. I suppose, it would be a mistake to have either the Renegade or his shadow on the throne of Gallifrey. Rassilon began to snort and grumble. In time it could be an unbearable condition to my more compliant subjects. Not to mention. Sighing deeply, he surrendered to two pair of intense, velvet eyes. All right, go on. Go back to that antique, rickety rackety, blue wart on my nose, if that is what it will take to make the two of you happy! Rassilon waved them aside. Go now! Before I reconsider, and take you free wills from you! The Doctors looked at each other for the time it took their twin hearts to complete a single beat each; then as one, bolted for the double doors. Hold! Rassilon demanded. The Doctors froze in their tracks. Turn, Rassilon commanded. Turning back to face Rassilon, the Doctors stared attentively. Keep the Time Ring on either one of yourselves at all times. If I require your services, dubious as they are, I want to be able to tether you back. Yes, Lord Rassilon, they replied. RING us up anyTIME, the little Doctor appended. AnyTIME at all, the Doctor echoed. Turning once again, each Doctor took a door in hand. One final thing. Doctors. The two looked over their shoulders. Keep the robes handy too. One day you might change your mind. Yes, Lord Rassilon, they promised. Each Doctor then opened a door. Before them stood the TARDIS, same as ever, frozen in its exterior guise of a blue, English police box, patiently waiting. With Rassilon and the Master tidily behind closed doors, the Doctors turned, eyeing each other gleefully, but it was he who challenged first. Last one in the TARDIS is an obdurate, obeisant, noninterventionist Time Lord! Well, in that case Doctor, the little Doctor contemplated, I'd for sure better rub The Holy Alabaster Monolith for luck! The what? A tiny fingered hand shot out to deftly stroke the Doctor's nose. Turning, with the grace of a will O' the wisp, the little Doctor charged towards the safety of the TARDIS, as peals of her gentle laughter bobbed softly on the air. Realizing he had been had, royally, a resonate baritone growl gurgled deep in the Doctor's throat. He quickly closed the gap between them, his total amassed sum of three hundred and fifty pounds not slowing him down in the least. Grabbing the little Doctor's shoulders squarely, the Doctor spun her round, just as she inserted her cipher indent key into its lock and started to turn it. Aug! Glaring at her, the Doctor removed her TARDIS key from the lock, pushing the door inward. Grasping her forearms, in the most menacing tone imaginable, he chuckled sinisterly, glaring down at her. Uh, oh, the little Doctor winced, slowly looking up at the Doctor in total abeyance. What happens now, dear Lord of Time? The muscles of the Doctor's face contorted horribly. Why my dear Doctor of Teeth and Curls I. She pouted. That should squelch any further attempt at sacrilege on my part. The Doctor shook his head sadly. Somehow, I doubt very much that it would, Renegade. Sighing deeply he slowly released his grip. Besides, a great waste of energy, for no guarantee. His intense, blue eyes began to sparkle animatedly. His face broadened out into its wide, toothy grin. Like an overjoyed school boy just let out on holiday, the Doctor bounded past the little Doctor into the TARDIS. Calling back to her over his broad shoulder he implored: Hurry along ol' girl, must dash, before Rassilon changes his mind! Indeed, ol' boy! Sprouting a grin to match his own, and with a gleam in her own soft hazel eyes, the little Doctor removed from a fold in the Presidential gown, her cream colored, tasseled recorder. Starting to play a tune, she shadowed the companion into the T. About those seven shades of blue scarves, Doctor, a distinctively masculine voice interjected. I've been giving it considerable thought. That particular scarf was a seven shade, earth tone one; and I wore it till it was threadbare and patchy. Not to mention. Moments later the light on top of the old battered police lock up box began flashing its white light. Straining and wheezing like a tired ol' grampus, the TARDIS slowly dematerialized, painstakingly slipping the two Doctors into Vortex. It all started with the nightmares. Strapped in a strait jacket, I find myself uncomfortably stationed on an hard metal chair, completely confined. Two uniformed police officers stand over me, staring down at me. One of the men in blue tugs forcefully on the jacket’s straps, verifying that they are secure. The second policeman jerks my feet up from the floor. Holding my shoes by the heels, he allows the other officer to lock a pair of inversion-boots tightly around my ankles. Next, using his teeth, he rips off a couple yards of duct-tape from a large gray roll. Together, the two officers wrap the tape tightly around and over the buckles, making absolutely certain the boots won’t fly open once I’m hanging up-side down. Unable to move neither my arms nor feet, I attempt to see just how tight the strait jacket is by wriggling back and forth in my chair. There is no give, no slack at all. Ha, ha, I guess I’ve gained a few pounds, I chuckle nervously, trying to relieve some of the tension in the air. However, their lack of response makes me even more uptight. The pressure from the heavily-starched white canvas is unrelenting, constricting my ability to take a full breath. I begin to hyperventilate slightly as my breathing is forced to become short and quick. My lungs, begging for more oxygen, cause my heart to pound strenuously against my chest. Desperate to calm my pounding heart, I whisper to myself, Don’t panic. Concentrate on what you are doing. Focus on the escape. It isn’t working – just the opposite. As my blood pressure increases, I begin to feel light-headed. The blood, pulsating against my eardrums, changes the dull thumping in my chest into a sharp throbbing in my head. Concentrate Jim! Panic and you could die! Gradually the thudding in my ears is replaced by the driving bass notes of some dramatic theme music. Over the loudspeakers, I hear the deep-voiced master of ceremonies announcing to the crowd, Ladies and gentlemen, children of all ages, you are about to witness one of the most daring escapes of all time. Even the late, great, Harry Houdini, never attempted anything like this! After being strapped in a regulation strait jacket and shackled by the ankles to a piece of rope, our magician, the amazing James Carpenter, will be attached to this two-hundred foot extension crane. Whereupon, the rope will be set on fire, the crane will be set into action, and our magician will go way beyond Houdini. He will be hoisted upside-down, two-hundred feet into the air. Remember, the only thing holding him up there will be a four foot length of burning rope. Check your watches ladies and gentlemen. The rope will burn through in approximately three minutes. If this daring escape artist does not release himself before the rope burns through – he’ll either have to learn to fly – or he’ll plunge two-hundred feet TO HIS DEATH! The crane starts up. The music builds toward a crescendo, quickly drowning out the dull roar of the crane’s diesel engine. After repositioning my chair to face the crowd, the police officers attach one end of the rope to the inversion boots around my ankles and the other end to the hook of the crane. With a wave from one of them, my beautiful assistant, her golden hair blowing in the breeze, steps up onto the platform carrying a fiery torch. Strutting across the stage in fishnet stockings, her long silky legs draw all the attention away from me. She leans forward, extending the torch which is now accompanied by a tremendous whooshing sound of the wind-blown flames. Almost in slow motion, I see the flame jump from the torch to the diesel-soaked rope, quickly igniting it. Within seconds the rope’s roaring like a blast furnace. I unsuccessfully struggle to take a full breath, coughing slightly after inhaling some of the burning diesel’s smoke. Concentrate. Try to relax, I repeat to myself in silence. With a sudden jerk the crane kicks into high gear and the cable hoists me upside-down, by the ankles. Looking downward, I see the ground pulling away rapidly, surprised at how quickly I’m pulled higher and higher into the sky. Twenty feet – I see the people in the audience very clearly from this height. Some have their arms crossed firmly, some applaud and cheer; others simply stare, their mouths wide open. Beginning my struggle against the strait jacket wrapped so tightly around me, I attempt to force my arms away from my body – the attempt is in vain. The jacket doesn’t give a millimeter. Sixty feet, and still rising – my body weight pulling down heavily on the hemp causes the rope to start untwisting slightly. Spinning slowly in a circle, I become aware of every motion, every slight twitch and pop of the burning fibers. Get out of this, I say to myself. You’ve got to get out! Wrenching sideways, I feel the rope make a sudden lurch down, frightening me. Time is ticking by as I make my way skyward. Eighty feet – losing my sensation of the crowd, my concentration now turns to the wind. With each gust it sways me slightly back and forth, creating red-hot flames and billowing a continuous cloud of black smoke into the blue sky. My eyes follow a small rainstorm of flaming diesel whipped away from the rope by the blustering air. Falling toward the earth, each droplet leaves a tiny trail of smoke, as proof of its existence, but disappears, consuming itself long before smashing into the pavement below. One hundred feet – with all the blood rushing into my head, I feel a kind of euphoria. Losing the up-side down sensation, I feel as though the world around me is inverted. For a brief moment my mind begins to wander, contemplating the vastness of the space around me and suddenly I feel very alone. Concentrate, I’ve got to focus! One hundred fifty feet – my struggle has now become a test of mental clarity as well as physical strength. My thinking is unclear. My arms are beginning to fatigue. Perspiration breaks out on my head and neck. Short of breath, I am starting to panic. My twisting back and forth becomes violent. I can’t get out! One hundred eighty feet – my enraged twisting yields a positive result as at last the sleeves gain some slack. With the extra space comes the ability to take a full breath and the sense that I’ll be okay. I just need to force my shoulder out of place for a moment. Pressing my right shoulder fiercely against the restraint, I feel a pop that goes along with a sharp, but temporary pain, Aaarrrgh! For a moment my shoulder is slightly separated; however, I now have the necessary room to get one arm out of its sleeve. A heavy sigh of relief – just a few more seconds and I’ll be out. Two hundred feet in the air – my arms are almost free; another distinct snap – not my shoulders this time. A burning ember brushes my cheek on its way down. Gazing up, time stands still for a moment. In horror, I watch as the rope separates. The small end of the burning rope, still attached to the crane, makes a flip skyward as if waving good-bye. The top of the crane pulls rapidly away from me. Oh my God! The rope is broken! I feel the sudden rush of momentum – downward. A terrifying falling feeling envelops me. The pavement races up to meet me head on. The crowd is screaming. I scream, Aaaaaaggggghhhhhh! Falling, falling … I close my eyes … falling. With the sudden lurch of the mattress beneath me, I practically felt myself hit the bed, waking up drenched in a cold sweat. Confused and lost for a moment, there in the darkness of my own bedroom, I could almost hear the faint echo of my own scream. But, as my eyes adjusted to the moonlight filtering though the blinds, I slowly regained my bearings and composure, realizing that it was all simply a bad dream. Somehow, during my sleep, the bed linens had become entangled around me – evidently the cause of the nightmare. After turning my night table lamp on, untangling myself took only a moment. To my misfortune, I discovered that during my nightmare I’d actually ripped a hole through one of the sheets. It must have happened while trying to free myself. The unconscious mind is a powerful force, I thought, slightly perturbed that I’d have to buy a new set. Taking a drink of water from the glass on my night stand, I relaxed, trying to reassemble the details of the nightmare. However, they were not very clear. I found that by the time I was completely awake, I had forgotten much more of the dream than I remembered. It had been a long time since I’d had a nightmare. I couldn’t really remember the last one and I was sort of glad that I didn’t remember this one. They happened a lot right after my father died, but that was when I was just a kid. That was a long time ago. Why was I having nightmares again? Why now? The next day was one of those warm, humid, autumn days in Austin, the kind that makes Texans wish for a change of season – boring, even monotonous weather, but nearly perfect for the Pecan Street Festival. Occurring twice a year, in both the spring and the fall, I always welcomed this outdoor festivity with all of its artsy-fartsy paintings and peculiar handicrafts. For the past six or seven years I had made a point of attending at least once each year. However, this fall, as I wandered through the street perusing the different vendors’ booths, I couldn’t help noticing that many of the arts and crafts were the same as the last time I attended. The festival, like most of my life, was beginning to look a lot like the year before. I too, found myself wishing for a change of season. Then I heard a voice, like that of a Shakespearean actor, booming out into the wandering crowd of festival goers, Ladies and gentlemen, children of all ages, step right up. The show is about to begin. Come see the incredible, amazing, astounding Maximillion as he attempts miracles beyond the concepts of human imagination! His words sent a chill up my spine, but not the kind that is a foretelling of something ominous, more the feeling you get when you're experiencing something extraordinary – like goose-bumps. I was intrigued by this deep and thundering voice of possibilities. Led by my own curiosity, I weaved my way through the crowd until finally coming to a clearing at the street corner. There, standing on top of a rather large, dusty old theatrical trunk, projecting all the enthusiasm of a ring-master on the opening night of the circus, was the magician. Waving a silver-tipped magic wand in the air while shouting his patter out to the crowd this engaging street conjurer made quite a striking impression. He was attired in a classic black tuxedo with tails, including a red satin vest adorned with sequined lapels which sparkled brightly in the afternoon sun. On top of his head, tilted just slightly to one side was the mandatory black top hat, the kind that pops open with the flick of the wrist. He also sported the standard, well-trimmed magician’s beard and mustache. From the streaks of silver-white at his temples, or the salt and pepper coloring of his facial hair, I would have guessed him to be in his late forties or maybe even fifty. But perhaps because of his physical condition, or from his youthful manner as he played to the crowd, he seemed to be much younger than the smile-wrinkles around his eyes, or the years of wisdom hidden behind them. Indeed, he had all of the trappings of a truly magical man. Well, not really a man, more like a riddle, an enigma. Half of him seemed fairy-tale wizardry – the other half, performer-reality. He looked as if he could really do magic – not just tricks – I mean real magic. I think it was his eyes; he certainly had the eyes of a magician. At times they sparkled more than his lapels. There was something about his smile, too. When he smiled, it was with a rather mischievous grin hinting that, behind those mystical blue eyes and that sly smile, he might be up to something devious. One thing about his appearance, though, did strike me as peculiar – kind of out of place. Dangling around his neck, I noticed a small silver chain. Where there should have been a medallion, or perhaps a crystal of some sort, attached to it; instead, pinned to the chain, with a simple steel safety pin, was a small square of tattered white cloth. The material looked to be nothing more than a small piece of an old rag or the corner of an old handkerchief. However, I concluded from observing the magician’s interactions with the strange necklace that it was possibly much more. At times, while he talked to his audience, the magician would rub this threadbare piece of cloth between his thumb and fingers, as if it were a good luck charm or magic amulet. Sometimes he would hold the piece of cloth and whisper to it. Perhaps this was just a nervous habit or (if I let my imagination get the better of me) perhaps the cloth contained his secret to some awesome powers over science and nature. Maybe this strange talisman contained his secret to the mysteries of life. Whatever it was, I knew that the cloth was important to him. Hurry, gather round, while the good seats are available, the magician proclaimed as he walked up to spectators who were intent on walking by and dragged them by the arm over to a predetermined spot. The magician was a true master at drawing himself a paying crowd. The unsuspecting onlookers would pause and sometimes laugh out loud, knowing that the fun was about to begin. Rarely did people seem unsure about joining in. But if they were, with a smile and a wink, the charming conjurer would always make them relax, kick back, and stay a while. So, did you two call ahead for a reservation? Was that smoking or non smoking? He took one of the attractive young girls by the arm, asking, Would you like to stand next to somebody famous? Then he said in a rhyme, You are, sweetheart. The amazing, incredible, astounding. Maximillion Vi! His resonant voice and cunning wit quickly attracted a sizable audience with two hundred or more people, young and old alike, now forming a circle around this unique street entertainer. I almost had to consider myself lucky; being one of the first to get there, I now stood at the front edge of his crowd. For his opening Maximillion Vi performed silent magic that truly was wonderful to watch. Like an elaborate dance, he pulled cards and silver coins out of thin air. Objects that he borrowed from the audience would vanish, only to re-appear under his hat or in a spectator's pocket or purse. The younger children enjoyed the show most of all – the kids, who had pushed their way through the crowd to the front row and now knelt or sat on the asphalt, pointing and poking one another, their eyes wide open in amazement. Most were hypnotized by the bewildering magician, as if he were a Pied Piper ready to lead them off to a better world. I, too, more than enjoyed his clever deceptions, the wonder and mystery of not knowing all the answers. In those mystical moments I became a child again, lost deep in the wonders of magic, trying to take it all in: the magician, the crowd, the sunshine. I recalled when I was the little boy, watching my first magician, clinging tightly to my father's hand. Just like the children kneeling in the street, I would have also pushed my way up to the front of the circle; because, when I was a little boy, I wanted nothing more out of life than to become a famous magician. Of course, those were just the dreams of a little boy. Watching the magician perform, recalling those memories, I flashed back my own childhood, in Springfield, Missouri, back to the time when I first decided to be, or perhaps discovered that I was going to be, a magician. My father had taken me with him to the smelly old junkyard, to help him dump a load of garbage. Dad loved to visit the junkyard; I never could understand why. The smell alone could almost kill a small boy like me. But Dad was always on the look out for something of value. One man’s trash is another man’s treasure, he’d say. That particular day, while we were unloading the trash from the pickup, my nose held with one hand, Dad spotted a potential treasure, a dilapidated old trunk lying in amongst the junk pile. With a little luck, a few hundred hours of sanding, he said that stinky old trunk could eventually become a coffee table: one with a new avocado-green, imitation-antique finish. The trunk was pad-locked shut so he couldn’t open it, but Dad picked up one end and gave it at shake. We could hear something inside, but couldn't tell what from the sound. The mystery alone made the trunk irresistible to Dad, and even caused me to forget the junkyard stench for a while. Dad used to say, curiosity is a sap running deep in the Carpenter’s family wood. After offering the junk dealer five dollars for it, the dealer countered with ten, and eventually they settled at seven. The dealer didn't know it, but Dad would have paid a lot more than seven dollars just to find out what was hidden inside. Mom often said that that was the sap he was referring to. We endeavored to open the trunk right then and there, but the lock was rusted solid. Dad decided, after beating on it with a tire iron for a short while, that even though both of our imaginations were working overtime, we’d simply have to wait until we got home. Upon returning home, my father immediately dug a hacksaw out of his trusty tool box and hacked off the rusted semblance of a lock. Opening the trunk, a puff of musty air greeted us. What we found inside may have been a little disappointing to my father, but was certainly a treasure to a seven-year-old boy. There inside, in almost mint condition, were several old magic tricks, an old bouquet of feather flowers, and three books on magic. Well, look at that. I guess destiny wants one of us to become a magician, Dad said as he tossed me one of the books. At that moment, I truly believed that fate had placed those objects into my hands, almost commanding me to learn the art of legerdemain. For some time after that, I remained enthralled with the art of magic, mastering the three tricks in the trunk: the linking rings, color changing scarves, and vanishing billiard ball. I read those three books until the pages practically fell out and went on to read several more books from the library about famous magicians like Houdini, Thurston and Blackstone. But I never really became much of a magician. Frozen in time for just a moment, I wondered, When did I give up that childhood fantasy? If I hadn't become an accountant, could I – would I – have ever become that famous, astounding magician of my childhood dreams? As soon as I began questioning myself, my positive energy dwindled away, my thoughts drifting elsewhere. I couldn’t help thinking about my job, Should I be wasting time playing games at the festival? Back at the office, I had unfinished work and I’d feel guilty Monday if I didn’t get it done over the weekend. Maybe I should just forget about spending the day … The magician began examining the crowd saying, I will need another sucker. He turned and looked square at me, almost as if he were reading my mind. You will have to do, he said, and before I could disagree, he grasped my arm and briskly walked me into the center of his circle. Somehow I knew that I would end up being the butt of the joke, making a fool of myself in front of the crowd, but I just couldn't find it within myself to say no. Allow me to introduce myself, sir, Maximillion said as he reached out and graciously shook my hand. I am the amazing, incredible, and astounding … Maximillion Vi … rhymes with free … which unfortunately is also what you work for as my assistant today. You may call me simply Amazing for short. Your name is? James, James Carpenter, you can call me Jim for short, I said, giving him a firm handshake along with my feeble attempt at wit. Smiling a curious smile, he pulled his eyebrows down as if he were going to ask me a question. His expression gave me a strange feeling, as though he knew me from somewhere before, or as if he’d been wanting to meet me for some time. Then something magical really did happen. While we were shaking hands, the magician reached up to the chain dangling around his neck, took the small piece of ragged white cloth between his thumb and fingers and began to rub it briskly. James, James Carpenter? Suddenly, a tingling sensation came over me, chills just as I had when I first heard his voice. Only this was much stronger: an energy, a feeling of excitement, a zest for life. This fantastic insight that life was truly magical, exhilarated me. At that moment I became acutely aware of my surroundings: the sun, the smiles, the magician. My vision even seemed to sharpen. Faces became brighter and clearer as I surveyed the audience: majorities and minorities, young and old, fat and thin; all laughing and smiling, all enjoying this unique moment. Why should I feel guilty about taking a day off? Everyone else was having fun. Why couldn’t I? Somehow, I knew I was being given this opportunity to experience some of life's real magic. I deserved a little magic in my life, too. Right then I decided that for the next few moments at least, I would just put away troublesome thoughts about my job, close the accounting books, and simply enjoy the magic. Amazingly, all of this went through my mind in that one short moment when the magician touched my hand and rubbed the small white piece of cloth. As abruptly as I had entered into this heightened state of awareness, I was pulled back into the present. Lost for just a second, I suddenly realized what Max was saying. Would you please act as the official timer for this act, Jim? Max asked. Sure, I replied. Does your watch have a sweeping second hand? The question made me glance at my wrist only to discover that I was no longer wearing my watch. I could have sworn that I was. Here, I guess I could let you borrow mine, Max said as he reached into his pocket and pulled out a watch – my watch! My chin almost dropped to the pavement. The crowd also realized that the he was now holding my watch and they laughed aloud. I couldn’t help chuckling too, awestruck that this man had taken off my watch without my having a clue. Deciding that maybe I should check for my wallet, I reached for my back pocket. I was possibly going to pay you for helping me out, but all you have in here is a couple of bucks, Max said, grinning. Again the artful pickpocket had duped me. He stuck my own billfold into my hand, then asked politely if he could borrow a one dollar bill. Folks, let me demonstrate how easy it is to drop a dollar into the hat. Saying that, Max tilted his head forward. With a quick snap from his neck, his top hat, like a gymnast scoring a perfect ten, did one complete revolution and landed open end up on the street in front of him. He held the dollar two feet above the hat and released it, the bill slowly drifted down like a feather into the hat. See how easy money goes into the hat, he said. Well, it comes out a lot quicker. With a snap of his fingers and a wave of his wand, the money defied gravity, shooting out of the hat and back into my wallet! I did a double take. You know Max, you could make a heck of a living doing that, I said, under my breath. I do make a heck of a living doing this, he whispered as he stuck his foot into his hat. With a kick up, the hat made an aerial flip and landed perfectly back on top of his head. Magic, I mean, he said, not missing a beat. The trick, he whispered to me, is not to make a living out of magic. The trick is to make magic out of living. He then winked and grinned, letting me know that I could trust him. It worked. For some reason, I did trust him, the same way a child might trust Peter Pan. Well, Jim have you ever seen one of these? Max asked assuming an air of importance as he turned around dramatically pulling a white canvas strait jacket out of the trunk. Yes, I have, I answered, not considering that I hadn’t actually seen a real strait jacket – only pictures of them. It makes me very nervous when volunteers answer yes, Max said as he looked all around the audience with this wide-eyed worried look. They laughed. Of course, Jim, you mean that you have seen them in pictures – not up close – right? Well, yes, I agreed, but somehow I had a strange feeling that I actually had seen one before. We are now going to test your strength, Max said. I asked you when you volunteered so graciously if you were indeed a man of constitutional fortitude and resolute dedication, did I not? Oh, I didn't? Oh well, you'll just have to do since you are standing here in the middle of my circle. With that he placed his hands on my shoulders and whispered a few strange words to himself which sounded Latin or ancient – that is, what I could hear of them. Placing one of his hands over my head, he gazed intently into my eyes. Next he pressed his index finger to the middle of my forehead, and began rubbing the small white piece of cloth with his other hand. Then Max spoke to me. You are now hypnotized, he said. Your arms, Max pulled my hands straight out in front of my body, they are steel! Again he rubbed the cloth pinned to the chain about his neck. They are beams of solid steel and cannot be bent – steel, Jim! As he said this, I indeed felt my arms become rigid and stiff. Could I actually be hypnotized? I attempted to discreetly move my arms. Not wanting to say anything out loud, so as to ruin his act, I just wanted to see if I really couldn’t move them. I could not. I tried harder; still I couldn’t move. Realizing that I was no longer in control, I started to panic. As if sensing my pending hysteria, Max again placed a hand on my shoulder, winked, and in a steady reassuring voice said, Don't worry; you are always in control – always. Nothing will happen unless you make it happen. He had read my mind. Immediately, I was comforted and relaxed. After all, what choice did I have? I had just started to enjoy myself, when I discovered how I was to become the butt of the joke. Your arms are frozen in front of you, he said, as Max proceeded to place the strait jacket – on me! The strait jacket was a coarse white canvas contraption covered with frayed leather straps and scratched steel buckles. The jacket showed years of wear and tear. Looking it over, I was sure it had been escaped from many times. I could also tell, that it was highly improbable – no, make that totally impossible – that once strapped in, I would ever be able to escape. After he had my arms strapped around my back, he gave the straps a couple of tugs and asked, Does that feel like a real strait jacket? Once again, without thinking, I answered, Yes. Max rolled his eyes, raised one eyebrow, made a face at the crowd asking, How do you know what one feels like? Once again they laughed and I laughed along with them; however, I really didn’t feel like laughing. For some reason unknown to me, I was overcome with a feeling of deja vu like I’d been in this predicament before. The feeling wasn’t pleasant at all; in fact, it was disturbing. I know I must have looked somewhat ridiculous, but one little boy was laughing so hard that the crowd began to laugh at him. He kept pointing and laughing, almost falling over. The boy’s laughter became infectious. Before long everyone in the crowd was enjoying the laugh-fest, everyone but me. Max had me totally strapped into the jacket – all except for one strap – the strap that buckles up underneath the crotch. Suddenly we all realized why the little boy had been laughing so hard. There is one strap left ladies and gentlemen and we call that strap – everybody say oooohhhhh, said Max. Everybody went, oooooooohhhhhhhh. There I was, standing with my arms crossed strapped behind me, struggling to move an inch, probably looking like some deranged lunatic, getting a strap of death wedgie. The crowd went wild. Max walked over to an attractive woman in the crowd and asked if she would assist us. She was a little bit anxious about the whole thing, saying that she didn't want to end up looking like me. Who could blame her? Max reassured her that she wouldn’t be put in the jacket, then snapped his fingers as if to un-hypnotize me. Stepping behind me, he unbuckled the straps to free me from the jacket. As I was pulling off the jacket, Max walked the beautiful woman by the hand to the center of the circle and introduced her, to the audience. Kristin, this is everyone, he said. Everyone, this is Kristin. Then handing the jacket over to Kristin, he stated, Just for the fun, I think that you two should put the jacket on me. With that he pulled off his tuxedo coat and satin vest, tossing them into the trunk. He placed his hands and arms into the strait jacket, which Kristin held open for him, and instructed me to step behind him and strap him in as tight as was humanly possible. With pleasure, I responded. Pulling the back straps taught, I could tell that Max was holding a deep breath, expanding his chest. All he had to do was release his breath and the jacket would be loose. Come on James, you can make it tighter than that can't you? Max yelled to the crowd. If you weren't holding your breath I could, I replied, trying not to sound too arrogant. With that comment Max let loose a puff of air that allowed me to tighten the jacket an extra two inches, as tight as his rib cage wnuld allow. Then Max asked me to pull the arm straps around his body and also tighten them as far as they would go. Practically hearing the compression in his voice, as if it were now even difficult for him to take a breath, I wondered if he would, in fact, be able to escape. So, feeling a touch sympathetic, I pulled the arm straps secure, but not too tight. Is that all the strength that you have? Max lectured. Put some muscle into it, James. Besides, don't you think the show will be better if I don’t get out? Okay, if that’s the way you want it, I responded, now pulling with all the force I could muster. Then in a comedy falsetto voice, Max said, Yes, by George, I think he’s got it. The crowd laughed. Looking at him now, there was no way in the world he’d ever get out of that strait jacket. He didn’t even have room to sweat. Max walked to the center of the crowd and in a loud deep voice said, Now ladies and gentlemen, as you can see, I am truly strapped in the confines of a regulation strait jacket. There is virtually no humanly possible way for me to escape. Uh uh, came the voice of the little boy who had been laughing so heartily earlier in the performance. He was pointing at what Max had so eloquently referred to as the strap of death, hanging precariously between the magician's legs, still unbuckled. Son, Max quipped in his theatrical voice, didn't anyone ever tell you that it’s not polite to point – especially in that direction. The crowd roared. Max, smiling that devious smile of his, turned his head slowly in the direction of the young woman, Kristin. Kristin? Max asked with a sheepish grin. You are probably wondering why I asked you to come here? Don't be shy. Just reach down between my legs and grab whatever you find dangling there. Again a chuckle from the crowd and Max continued, This strap doesn't have to be as tight as the others. Tears formed in my eyes from holding back the laughs, at this farcical scene. Kristin, bending down behind him, reluctantly reached between his legs; Max would squirm just as she reached for the strap, swinging it out of her reach. After several failed attempts she grabbed it and began buckling the crotch strap together. You sure are taking your sweet time, Kristin. You’re enjoying this way too much! Max teased. The crowd began yelling, Tighter, make it tighter! Go ahead and pull it tight, Max said and then whooped, Waaaaaaiiiit, not that tight! Kristin ignored his antics and buckled the strap tightly. After which she stood straight up signifying that she had indeed strapped the escape artist in firmly. Max then acknowledged, Let’s give Kristin a big Texas round of applause. Thank you for being such a good sport, Kristin, and helping us make the world a little happier, and certainly a safer, place. I want you all to know that y’all are enjoying this a lot more than I am. The crowd applauded for Kristin as she smiled and took her place back amongst them. Max moved back into the center of the circle, calling to the audience, Ladies and Gentlemen, does anyone happen to know the world record for the escape from a strait jacket? Houdini could escape in less than one minute. The incredible Loren Micheals could escape in less than forty seconds, The Amazing Randi in less than thirty. However, the world record for the fastest escape from a regulation strait jacket happens to be. And do you know who happens to hold that record? Today, however … I – the incredible, amazing, astounding, Maximillion Vi – will attempt, for the first time in Austin, Texas – for your sheer and utter enjoyment – NO SUCH THING! Then Max remarked in exaggerated Jewish accent, I can't get out of here in that short of time. This is really tight! What do you think. I can do miracles? Strange that he would ask such a thing. He could do them; miracles, yes that was exactly what the audience expected – exactly what I expected. Only moments ago Max had seemed omniscient, capable of miracles; now in the strait jacket, he appeared to be a mere mortal like the rest of us. However, I had the distinct feeling that his distressed mortal look might be just that – for appearances. Max continued, losing the accent this time, How about if I escape in a reasonable amount of time? Is there a reasonable person among us, who could suggest a reasonable amount of time for my liberation from these bonds – the likes of which, even the great Houdini never felt? A few persons in the crowd started to shout out times. Fifteen seconds. Ten seconds I said reasonable, Max grumbled. Thirty seconds, I said, thinking it was reasonable. With that he turned back to me and said, What time did you suggest? Thirty seconds. I repeated. What's that again? Louder, for everyone's benefit, Jim, Max said, leaning closer to me as if I had stumbled upon the proper time. Thirty seconds! I shouted out. At the same time Max yelled, TWO MINUTES! The man says I should attempt to escape in TWO MINUTES! As the crowd laughed, I was beginning to understand the real magic that Max Vi held. People loved him – that was the magic. Max continued, Very well. I will attempt to escape from this strait jacket within the constraints of a two minute time limit – even though such a release may appear to be a virtual impossibility. Ladies and gentleman, I have to ask you to trust my official timer, Jim. Jim, you are going to have to keep me posted. When one minute has passed I want you to yell out. One minute! At one minute and thirty seconds I want you to yell out. He made a motion for me to fill in the blank. One minute and thirty seconds! I shouted. And at one minute and forty-five seconds, James, I want you to yell out. I took the bait and yelled out, One minute forty-five seconds? Wrong! Max said, making a loud obnoxious sound like a buzzer on a game show. GZZZZZZ. No James, when I reach one minute and forty-five seconds I want you to start counting down. Fifteen. Got it? Got it. I replied. Max announced loudly to the crowd, And everyone will start counting down with Jim, right? A few of the more vocal ones shouted back the answer, Right. But the response was not overwhelming and certainly not satisfactory to Max Vi. He repeated, And everyone will start counting down, right? Right! And should I escape in those last few seconds counting down four, three, two, one, everyone will burst into a thunderous round of applause! Right? Right! The crowd screamed back like a cheer leading squad. Screaming and cheering – RIGHT? Max yelled back even louder. RIGHT! REACHING FOR YOUR WALLETS! Max yelled, raised one eyebrow, paused for effect, breaking the rhythm. Some started to respond, but after they realized they’d been led down this path, the crowd laughed. He paused. His eyes took on a steel gray concentration and he inhaled a deep breath. Turning to me, he stated was ready to start. On your mark … Get set. I paused to let the second hand sweep to the start position. Max stood poised. GO! The incredible Max Vi shrugged his shoulders, grimaced, clenching his teeth while twisting violently back and forth. I looked at my watch; time was passing quickly. Thirty seconds and the magician's struggle didn’t reveal so much as an inch of slack. The strait jacket held firm. The first order of the day is, Max announced already half out of breath, the strap of death. With that announcement Max , still secure in the jacket, sat down on the street and kicked off his shoes and socks. He quickly worked his way into a kneeling position and reached for the crotch strap between his legs with the back of his feet. Slowly, like a contortionist, he pulled the strap up with his toes and unbuckled it. To watch him stretch his body to the very limit, almost made me hurt. Amazing. Glancing again at my watch, one minute had already expired. One Minute! I yelled out just as he had instructed earlier. Not yet! Wouldn't you know I would have to find the one person with the Quartz Acutron watch, Max grumbled for a laugh. With that remark, the incredible Max Vi stood up in his bare feet, breathing deeply to regain his strength. His face remained tense and contorted, until with one long slow breath he suddenly relaxed. All of the jerking and struggling stopped. His face became poker-playing deadpan. Determined, painstakingly he lowered one of his shoulders. He stared straight ahead with intense concentration. I checked my watch. He was still a far cry from freedom. One minute thirty seconds! I yelled. His hand barely moved under the jacket. Sweat dripped from his forehead. The seconds ticked by. Max concentrated all of his efforts into moving just one hand. Fifteen. I shouted, and the crowd joined in. I couldn't see any movement at all on the part of Max. My heart began to pound. He was not going to get out. I heard a popping from his shoulders that caused him to wince in pain and groan aloud. FOUR. His arms flung free from his body and over his head. THREE. TWO. A quick strong jerk and the strait jacket burst up high into the air. ONE. Max Vi was free! The crowd exploded into an ovation. With a quick sigh of relief, I began to applaud and cheer with them. Max waved the strait jacket in the air with one hand, reached over and grabbed his hat with the other. Turning in a circle, he exclaimed, If you appreciated the show, please show your appreciation! The crowd responded in kind, with people digging out money from pockets, purses, and wallets. Parents entrusting their children with the change or dollars, instructing them to place it inside the magician’s hat. I observed one grandpa, so pleased with the show that he presented his grandson a crisp ten dollar bill to add to the pot. Having completely regained his breath and now showing no signs of his momentary struggle, Max said, I would like to thank you – all of you – by leaving you with one last miracle. Then he turned to me, instructing, Jim, if you would please, collect the rest of the money. When you are finished just place the hat and money inside this old trunk. He walked over to the trunk full of props and pulled out a large red satin sheet, closed the trunk and returned to the center. Holding the sheet behind him and above his head, turning around in a circle, he called to the audience, Ladies and Gentlemen, please take this one small bit of magic into your lives. Learn that life itself is the magic! Every second conceals within it a lifetime, every minute an eternity. Learn to live each moment of life as if it could suddenly disappear. Max then lifted the sheet above his head, covering his entire body. Pausing for a moment of silence, he then just simply vanished. There is no other way to explain it. He faded into nothingness, the satin sheet casually drifting to the street below as though he had slowly evaporated. The crowd was silent. We all gawked at each other, expecting that he would somehow appear in the next instant, but after an awkward minute he still did not. A few people started a rather weak round of applause, but the illusion had been too astounding, too real, too stunning, almost to the point of being surreal. He had been standing in the middle of the street, surrounded by a crowd of people! There was just no way his disappearance could have happened beyond black magic or witchcraft. An aging white-haired woman walked forward, and placing a dollar into the hat she broke the silence, saying, You two fellows put on one heck of a show. Her gesture of good faith started a new round of applause and brought a new stream of money flowing into the hat. Somewhat confused, I couldn’t resist a quick bow to the crowd. Even though I knew it was wrong, I couldn't help wondering what would happen if I just took his props, money, and strait jacket and became the magician of my childhood dreams. Where was Max Vi anyhow? Maybe he had vanished for good. The crowd slowly disintegrated, just like the magician, soon transforming into a constant flow of people wandering through the busy festival thoroughfare. Only one other person remained standing in the same place after the crowd had dwindled. Standing alone at the back of the walkways was Kristin, the beautiful woman who had assisted in the show. She strolled up to me, smiling. You are part of the act, aren't you? No, I really wasn't. I was just a volunteer, like you, that he pulled out of the crowd, I replied. Well, where is he? Knowing that I didn’t have an answer, I just laughed, I suppose he'll show up for his money sometime. Why don't you and I just take off with it? Come on, buy a lonely girl a drink. Even though it was the best offer I’d had in ages, I just couldn't leave without seeing that the magician had his hard earned cash. That is tempting, but I think that we can manage without taking away his hard earned cash, remembering just then what I was supposed to do with the money, Wait just a minute, I said. Then I walked over to the magician's old trunk, unlatching it to place both the money and hat inside as I’d been instructed. Startled, I jumped back, as up out of the trunk popped Max, Congratulations, you've found me! I was beginning to think that you’d never open this darn thing. Right in front of me stood the amazing Max Vi – truly a magician's magician. How on Earth did you get into the trunk? I asked. What makes you think it was on Earth, James? Max asked rhetorically, Sometimes the questions aren't as obvious as the answers. That’s why I suggest that people don't dwell too much upon questions. You see, it’s more often the questions themselves that keep you from seeing the answers. Just concentrate on the reality, not the illusion, and you will see that the answers are always right in front of you. Your life will give you the answers. That is, if you stop confusing yourself with too many questions. I don't understand what you mean, I said. Max answered, Well, isn't it amazing that I am here, in the trunk? Isn't it amazing that you were here today, and the only one who found me. There is a meaning in it, James there’s meaning in any amazing coincidence. The question itself is the answer. It’s magic! And, Jim, it’s only magic if you have a question. I just stared at him mouth-open, perplexed and maybe even slightly flustered by his strange double-talk. Then I asked, Is it real magic, or is there some sort of a trick to it? One man's trick is another man's treasure, he replied. If you really have to know, I'll tell you. I always tell my good assistants. But, before I tell you, I must warn you that by telling you the secret, the magic itself will disappear. Once it does, then only real magic can bring it back. He paused, looking at me for some semblance of understanding. Although, I didn't understand most of what he had said, I realized in my heart that I really didn't want to know the secret. He was right. Knowing would spoil the fun, so I shook my head no-thanks. Good choice, Jim, Max continued, a lot of the time, people come up to me and demand to know how I do these amazing things. I wonder to myself, ‘Why do they have to ask? If we enjoy the magic then what is the purpose of asking how? If we were all magicians, then where on earth would we find magic? When the sun rises, sometimes isn't it just enough to feel the warmth – to see the sunlight spilling over the countryside? Do we have to know that it is a fusion reactor, spewing photon particles across space? Sure it's nice to have a weather forecast. But sometimes an unexpected shower can be revitalizing, don’t you think? Imagine just how boring life would be if you and I did know all of the answers. Too many of us spend too much time looking for the secret, ‘the how,’ when the answer is the magic itself, ‘the why. Two things that I have learned in this life are: one, that you never ask a magician how he does his tricks; and two, you never, never, ever ask why. With that she threw her arms around Max, and they embraced with a short, but affectionate, kiss and hug. Jim, allow me to introduce my assistant and wife – the incredible, loving, tolerant, Kristin, Max said with a wink. Now, I truly felt like the fool. I should have guessed when she offered to run away with me, I said. She always does that. It’s part of the test, Max said nonchalantly as he pulled on an old football jersey, the number zero, over his tux shirt and began to pack up his tricks. Test? I asked with more than a touch of that Carpenter curiosity. Before he could answer, a couple of youngsters who had watched his act reappeared, asking Max for his autograph. The magician cordially responded by digging in his trunk for some of his black and white promotional photographs. After getting his signature on them the kids ran down the street ecstatic with their new treasure. Max then lifted the hat full of money; weighing it in his hands for a second, he announced, One-hundred-seventeen dollars and forty-seven cents. Would you check that for me? You are an accountant aren't you? Max asked. You account and I'll tell you about the test. The sun was just setting; the festival was winding down and many of the booths were closing shop. At Max’s request, I started doing what I was supposed to be good at, counting money. James, Max began every year I perform the escape and vanish seven-hundred seventy-seven times. Sounds amazing doesn’t it? Actually I use that number just to make the story interesting. Really, I have no idea how may times I do that particular act each year, probably somewhere around fifteen, I suppose. Well, anyway, I have been doing that act since I was about a year older than you are now. How old are you anyway? Twenty-eight, I replied. He laughed and pointed to my handful of bills, knowing I was in the middle of counting and that his questioning would prove to confuse me. It did; I lost count. But I just chuckled and started over. Well, I will actually turn twenty-nine in a couple of days, I added. Exactly, Max stated, I started the escape act at thirty years of age. Anyway, I have been performing around the world, in twelve languages for about twenty years. Each and every time, I have a volunteer, like yourself, assist. In all of those shows, in all that time, I have met only three other people who demonstrated the same qualities you possess. But unfortunately all three failed the test. Meeting you here today was no accident. Fate threw you into my circle for a reason. He then placed his hands on top of mine to make me stop counting the money and said, You can feel it too, can’t you? I’ve been looking for you for a long time – James, he said, you are the one. Pulling his eyebrows down into a serious look right at me, he stated, I want you to take this money home with you and count it. Bring the money back to me next Spring, if and when you decide to come to the festival. If you can't come, or don't wish to, then you keep the money for yourself. I know it isn't very much to a yuppie guy like you, but you might have some fun with it, just the same. Maybe you’ll take a good-looking girl out for dinner. Why would he give me the money and ask me to return it the next year? What did he mean by the qualities that I had? I was curious to say the least. I've got everything put away. Should we disappear? Kristin asked. Wait a minute, I said. What do you mean? What do you want with me? One second sweetheart … I think that James is truly the one, Max said, pushing both the money and hat back into my hand. James, if you want to learn the true secrets of life’s magic then you must first accomplish a great feat. What feat? What do you mean? I asked. James, you must be patient and observant. If you are patient, in time, life will reveal its greatest secrets. If you are observant you will learn to recognize them. James, always be on the lookout for the magical opportunities in your life. The magic life will be yours only if you explore them. This could be one of those magical opportunities. Every second conceals within it, a lifetime – every minute, an eternity. Great for you, Kristin called to me, I look forward to seeing you next year. She then walked over to me, reached out and took my hand in hers. Standing directly in front of me, smiling her nearly angelic smile, she gazed up at me and said, Thank you, Jim, for participating in my life. With that she rose up on her toes and leaned her face forward to kiss me. Naturally, I closed my eyes as I felt her warm lips softly press against my own. The moment was very fleeting. Kissing her softly, her warm touch slowly vanished. Her hands seemed to vaporize in my grasp, leaving me holding nothing but air. Suddenly, I was aware of the cool wind and the empty streets as I opened my eyes to discover that I was standing at the curb, alone – not a trace of Kristin or Max. The sun was now below the horizon and the evening breeze whispered around me. I stood there for several minutes staring down at my watch in disbelief – it was late evening. I wouldn't have believed that any of it had ever happened, but, like an experience out of the Twilight-Zone, there in my left hand was the magician's top hat filled with dollars and coins. As I made my way back across town, to the parking garage, I played the strange scene over in my mind. I could visualize Kristin and I strapping Max into the jacket; I could see the agony on his face as he pulled himself free. I could see him vanish under the cloth. But I couldn't see how it was possible. It all seemed like a dream: the feelings, the small white piece of cloth, the test. What did he mean when he said I was the one? DJ's Turntable, available in both black and silver. New in the box. Pro-Logic Mini System, 219 Watts. BeoVision Avant 32 puts the viewer at the centre of events, with a smooth and all encompassing wide screen picture and a truly convincing sound. Using four active loudspeakers front and rear, and a centre loudspeaker for voice reproduction, the Dolby Surround Pro Logic principle creates a spatial illusion that a cinema can hardly match. The BeoSound Ouverture serves as the musical centre with CD, tape and radio. NTSC playback. Built-in Dolby Surround Pro Logic module. VisionClear. Anti-reflective contrast screen and picture tube. Format optimisation. Active loudspeakers. Remote controlled motorised stand. Electronic curtain. Improved Teletext. Remote control. Connections for camcorder and headphones. Optional modules for satellite and picture-in-picture. Available in pearlescent shades of green, red, grey, blue and black. CD player with programming. Tape recorder with auto reverse. FM/AM radio with pre-set. Timer function. Optional remote control operation. Optional wall bracket and floor stand. Designed to stand out from, and blend in with, your furniture at the same time. Active loudspeaker. Adaptive bass linearisation. Aluminium cabinet. I am cold. I am fond of reading. I became familiar with him. I beg to differ. I call it a day. I called at your house. I can do it on my head. I can ill afford it. I can make nothing of it. I can't afford a car. I can't afford it. I can't bear him. I can't bear this heat. I can't do anything with it. I can't help doing it. I can't help it. I can't make head or tail of it. I can't possibly do this. I can't refuse her anything. I can't tell you off-hand. I carried my point. I chanced to hear it. I chanced to meet her. I couldn't get a word in edge-wise. I count on you. I dare say it's only a matter of habit. I dare say you're right. I dare say. I dare you! I decided to keep him at arm's length. I didn't catch the sentence. I didn't get a wink of sleep. I don't care a fig for it. I don't care a pap for it. I don't feel up to the mark. I don't give a damn. I don't like his suggestion at all. I don't like the look of it. I don't mind! I don't see the fun of it. I don't see your conclusion. I enjoyed my supper. I envy your calm. I fail to see what you mean. I felt impelled to say it. I felt very uncomfortable. I get it. I get my watch an hour ahead. I got soaked to the skin. I had a job to do it. I had her sit down. I had it done. I happened to meet him. I have it from a good source. I have it sent to me. I have no objections. I have no small change. I have not the faintest idea. I have to do an errand. I have to start bright and early. I haven't a penny to my name. I haven't any money. I haven't got a clue. I haven't the faintest idea. I heard it this morning on the radio. I hope you'll back my plan. I insist on obedience. I intended it for a compliment. I just managed it. I kind of thought. I know about it. I know better. I know him by sight. I know my own mind. I landed him one in the face. I like that! I like the way he works. I meant no harm by it. I missed the connection. I need some rest badly. I only said it in fun. I owe him much. I paid him out in his own coin. I pity you. I put all my eggs in one basket. I put my shirt on that man. I quite believe it. I refuse to be rushed. I say! I should prefer to wait until evening. I should prefer to wait. I slept like a top. I stand to win. I stick on the stamp. I stick to it. I think I've been done. I think he's out of the wood now. I told him what was most important. I told you so. I want to read in peace. I was completely browned off. I was cordially received. I was ill at ease. I was not born yesterday. I was terribly bored. I wash my hands of it. I won't argue that point. I won't lift a finger. I won't stand that. I wouldn't put it past him. I'll act on your advice. I'll arrive a week tomorrow. I'll be damned if I know! I'll do it this minute. I'll give him what for! I'll give you the low down. I'll give you what for! I'll just drop in for a second. I'll never make it. I'll see to it at once. I'll see you further first. I'll see you home. I'll think it over. I'm a stranger here. I'm all mixed up. I'm annoyed about it. I'm at a loss for words. I'm at a loss what to do. I'm badly off. I'm blessed if I know. I'm broke. I'm bursting with curiosity. I'm curious about it. I'm dreadfully sorry. I'm fed up with it. I'm fed up with strikes. I'm glad to meet you I'm in no laughing mood. I'm in the dark. I'm looking forward to seeing you again I'm not going to mince matters. I'm not taking any! I'm of good cheer. I'm off. I'm on my way! I'm quite put out about the matter. I'm relieved to hear that. I'm short of breath this month. I'm sick and tired of it. I'm sorry for her. I'm sure I don't know. I'm tired of it. I'm under notice to leave. I'm very much in favour of it. I'm waiting to hear your explanation. I've a bad cold. I've an upset stomach. I've been extremly busy lately. I've changed my mind. I've finished with you. I've got it. I've improved a lot. I've known it to happen. I've my back to the wall. I've no preference. I've nothing on tonight. I've other fish to fry. If I only had known! If only I had more money. Is anything wrong with you ? Is anything wrong with you? Is he a glutton for work? Is there any juice left? Is there enough wine to go round? Is there such a thing ? It allows of no excuse. It baffles description. It beggars description. It came to nothing. It can't be helped. It cramps my style. It crossed my mind. It doesn't make sense. It doesn't pay. It escaped my notice. It gives me the willies. It grew cold. It isn't much good. It isn't my job. It leaves me cold. It lies with you to do it. It looks like rain. It makes no odds. It means a lot to me. It never rains but it pours. It offends my eye. It rings true. It runs in the blood. It serves no purpose. It struck me right away. It tastes of salt. It tells its own tale. It took a load off my mind. It was a put-up affair. It was a scene of destruction. It was a stormy affair. It was brought home to me. It was either do or die. It was his off day. It was learned yesterday. It went like clockwork. It will do tomorrow. It won't work. It would be no good. It's Hobson's choice. It's a bargain! It's a beastly shame. It's a busy street. It's a closed book. It's a deal! It's a great convenience. It's a matter of common knowledge. It's a matter of life and death. It's a red rag to him. It's all Greek to me. It's all haywire. It's all in the day's work. It's all make-believe. It's all over. It's beyond her grasp. It's blowing a gale. It's carrying things too far. It's clear as daylight. It's do or die now. It's due to him. It's excellent value for money. It's foul weather today. It's hard on me. It's high time. It's his fault. It's in her nature. It's merely a matter of time. It's much the same thing. It's my fault. It's neck or nothing. It's no end of trouble with him. It's not in him. It's not worth worrying about. It's nothing to write home about. It's only a stone's throw from here. It's past comprehension. It's perfectly silly. It's pouring with rain. It's raining cats and dogs. It's safe to say. It's sink or swim. It's the real McCoy. It's understood. It's up to him. It's very good of you. It's within my grasp. It's worth the trouble. It's your funeral! It's your turn. His bark is worse than his bite. His days are numbered. His dreams came true. His face fell. His face was a perfect study. His fingers are all thumbs. His friends are few. His life is at stake. His life is no bed of roses. His name escaped me. His number is up. His race is run. His report caused a sensation. His salary was cut. His time is up. Hit or miss. Hold the line! Hold your horses! Hold your noise! How dare you? How did you hit on that? How does it strike you? How is the peseta today? How now? How sad a fate! However you do it. Just as you like. Just follow your nose. Just go and try it! Just imagine! Keep it dark! Keep it under your hat! Keep moving! Keep off the grass! Keep out of mischief! Keep out! Keep your hair on! Keep your powder dry! Keep your tail up! Keep your temper! Life isn't all beer and skittles. Get a grip! Get along with you! Get cracking! Get lost! Get me the file. Give him beans! Give him his due. Give me a call! Glass! handle with care! Go along with him! Go and have a wash! Go in and win! Go to hell! Have a go at it! Have a smoke? Have it your own way. Have you put on some water? He advised against it. He appears to be sick. He applied for the job. He asked to be excused. He bears no enmity. He bore the palm. He breaks a fly on the wheel. He brought home the bacon. He came by himself. He came by the same token. He came off a loser. He came to her aid. He can't make head or tail of it. He can't take a joke. He cut a caper. He departed from his word. He did all the talking. He did it on purpose. He didn't even begin to try. He didn't say anything at all. He didn't seem to be quite all here. He died of a heart attack. He doesn't hold a grudge against him. He doesn't know how to behave. He doesn't mince matters. He drives a hard bargain. He entered the name in a notebook. He failed to appear. He fell in love with her. He fells nohow. He finds no quarter. He found plenty of work to do. He gets about a lot. He gets away with everything. He gives him the fluff. He had a fine old time. He had forty winks. He had his hair cut. He has a finger in the pie. He has a sharp tongue. He has a very keen mind. He has bats in the belfry. He has been to see the doctor. He has brains. He has found me a job. He has him on toast. He has lost his wind. He has no kick left. He has not a spark of decency. He has not his fellow. He has two teeth missing. He has visitors. He hasn't got it in him. He is all talker. He is full of beans. He is like that. He is to blame for it. He isn't here any more. He just escaped being killed. He keeps a civil tongue in his head. He kept his head above water. He kept me from work. He kept the pot boiling. He knocked the stuffing out of him. He knows a thing or two. He knows his onions. He knows his stuff. He lacked the courage to do it. He lacks the courage to do it. He learns the hard way. He lives the life of Riley. He made a bolt for it. He made it his business. He makes himself scare. He makes things hum. He managed it in the twinkling of an eye. He may come tomorrow. He means business. He met his Waterloo. He met his fate calmly. He needs helping. He never loses his poise. He nurses a glass of wine. He plays a good knife and fork. He plucked up courage and went on his way. He pokes his nose into everything. He poured the money down the drain. He put his oar in. He puts a good face on the matter. He quaffed off the wine. He registered every word. He rests on his laurels. He saw daylight. He saw the red light. He showed it for all the world to see. He shrugged his shoulders. He simply doesn't count. He slept away the day. He smells a rat. He spoke about indifferent topics. He sprained his ankle. He stands upon trifles. He stops at nothing. He stroke for home. He takes a line of the least resistance. He talks Billingsgate. He talks big fam. He talks his head off. He thinks no end of himself. He thinks nothing of it. He thinks the world of you. He throws his cap over the mill. He told it to me in confidence. He told me a lie. He took his chance. He took revenge on him. He took the gloves. He tried to palm it off on me. He tried to prove that black is white. He tried to save his bacon. He turns the tables. He was as bold as brass. He was completely lacking in courage. He was delayed. He was easily persuaded. He was faced with ruin. He was taken in by him. He was very off-hand. He will live to rue it. He won't set the Thanes on fire. He won't stir a finger. He worked wonders. He works from morning to night. He works to rule. He wouldn't give it house-room. He wound up in a pub. He wrote a novel with a kick. He'll never get anywhere. He'll take no refusal. He'll take the chance. He's a chip of the old block. He's a con man. He's a dead loss. He's a decent fellow. He's a deep me. He's a dog in the manger. He's a few years under age. He's a glamour boy. He's a goody-goody. He's a hard man to please. He's a heavy drinker. He's a hen-pecked husband. He's a knowing me. He's a man about town. He's a man of few words. He's a pain in my neck. He's a queer card. He's a sad dog. He's a sly old dog. He's a stay-at-home. He's a thorn in my side. He's a wet blanket. He's all ears. He's an awful bore. He's an old stager. He's as stiff as a poker. He's as thick as two short plankes. He's at odds with his friend. He's bad medicine. He's behind the times. He's being helpful. He's beyond help. He's certain to come. He's clean-shaven. He's dead tired. He's entirely in your hands. He's full of go. He's full of mischief. He's going strong. He's gone for a few days. He's good at French. He's half-baked. He's hard up. He's head over ears in love. He's in a quandary. He's in his prime. He's in the dark. He's late as usual. He's like a drowned rat. He's no match for him. He's not much of a dancer. He's not suited for a doctor. He's nuts about her. He's of another opinion. He's on his beat. He's on his last legs. He's on his own. He's on the dole. He's on the make. He's on the mend. He's only pretending. He's prejudiced. He's round the bend. He's said to be a rich man. He's sure to succeed. He's two-faced. He's up to every trick. He's up to him. He's wide awake. He's wrong in the garvet. He's yellow! My feet! My goose is cooked. My hair stood on end. My head swims. My pain has gone. My sight is very poor. My turn will come. Never mind! Never say die! No ceremony! No fear! No idea! No kidding! No offence! No opportunity offered itself. No person other than yourself. No smoke without a fire. No way! Nobody cares two hoots about it. None but fools! None of your excuses! None of your lip! Not for the life of me! Not half bad! Not just yet! Not so dusty! Not that I remember. Now I begin to see. Now less then ever! Now you're talking sense. Nuts to you! Of what avail is it? Off you go! Owing to the rain we couldn't come. Money can't buy it. Money is no consideration. Money is no object. More power to you! May I have the floor. Need he do it? On with the show! Admission free! All jokes aside! All seats are taken. All the same she came. All things are alike to him. An idea rushed into my mind. Anything else ? Are you full? Are you in a draught? Are you in earnest? As far as I know. Ask her in. Bad needs grow tall. Be ashamed of yourself! Be frank with me! Be yourself! Bear in mind that you've an appointment. Beggars can't be choosers. Behave yourself! Bless my heart! Bring me the tools;will you? Buck up! But you know that! Can I give you a hand? Can you swear to that? Charly was an out and out student. Cheer up! Children not admitted! Come along! Could you go and get the paper? Curse him! Cut it out! Darkness had come. Dear me! Did you ever! Did you get the sack? Did you have a nice time? Did you look at yourself in the glass? Did you straighten out the matter? Didn't I tell you before? Dirty trick! Do you carry silk? Do you ever see him? Do you feel hungry? Do you have a light ? Do you play the piano? Do you plead guilty? Do you remember me? Do you see the point? Do you take me? Do your worst! Does that name ring the bell? Does this make sense to you? Don't I know it! Don't be cross with me. Don't be down in the mouth. Don't be so fussy! Don't beat about the bush! Don't bother about it. Don't bother me! Don't bother! Don't darken my door again. Don't fail to come! Don't fuss! Don't get discouraged. Don't get out of patience! Don't go on like that! Don't halloo till you're out of the wood! Don't interrupt me! Don't keep asking me. Don't keep crying! Don't lay the blame on me! Don't let it trouble you! Don't let me keep you. Don't let on anything! Don't let yourself be fooled. Don't lie to me! Don't look like that. Don't lose heart! Don't make me laugh! Don't meet trouble halfway! Don't mention it. Don't put the saddle on the wrong horse! Don't run away with the idea. Don't take it amiss. Don't worry! Draw a deep breath! Draw it mild! Draw the curtains! Drop me a line. Drop me at the corner. English is spoken all over the world. Every little counts. Everything at the proper time. Everything hangs on your decision. Excuse my interrupting. Fancy meeting you here! Fashions pass. Fire away! For all I care. For me she's too frivolous. For what reason? From now on it's all plain sailing. Good speed! Hands off! Has she a temperature? Hence with it! Her new hat becomes her. Here's to you! Homer sometimes nods. Hook it! Jump in the lake! Language;sir! Let her rip! Let's face it. Let's play it safe. Look ahead! Look out! Make a left turn! Make haste! Make it snappy! Make yourself at home. Man alive! Mind the step! Mind your own business! Once bitten twice shy. Paper doesn't blush. Passengers alight here. Pay close attention! Pay heed to what he says! Pepper is hot. Peter has a friend waiting to see him. Play up! Please be on time. Please clear the table! Please forward! Please see to this while I'm away! Poor old chap! Practice makes perfect. Pray;consider! Prices are on the decrease. Prices continued to rise. Pull your finger out! Pull yourself together! Put something by for a rainy day! Put yourself in my place. Queen Anne is dead! Quit joking! Rank nonsense! Rats! Same here! Serves him right! Sharp's the word! She arrived by bus. She came last. She came up from the country. She couldn't hide her emotion. She decided to give him a wide berth. She disburdened her mind. She doesn't begruge it to you. She doesn't care a hang. She doesn't know her meter and bounds. She doesn't know the first thing. She doesn't look her age. She dropped in. She drove me crazy. She feels hurt. She feels sick. She flatly denied. She gave herself up to him. She gives her fancy full scope. She gives me a lot of trouble. She had a hair-do yesterday. She had a ripping good time. She had no money about her. She had pity on him. She had to swallow a lot. She has a cold today. She has a slight headache. She has no feeling for art. She has no imagination. She has not a rag to put on. She hasn't got to go. She is out of town. She laid the table. She listened in silence. She lives one floor below me. She looked surprised. She nodded her head. She paved up and down. She refused to believe it. She speaks in images. She swept from the room. She tried hard. She was a vision of delight. She was at the wheel. She was fast asleep. She was offended. She was very outspoken about it. She wasn't allowed to go. She went over her notes. She's a bad sailor. She's a glutton for books. She's a lying so-and-so. She's a nobody. She's all the world to him. She's an early bird. She's back home. She's bent on going. She's bursting with curiosity. She's cracking up. She's crazy about television. She's dressed up to the nines. She's good at sums. She's grown very thin. She's head over heels in love. She's herself again. She's in a fever of excitement. She's in a jam. She's in his good books. She's not at all musical. She's not in. She's of French stock! She's only trying to poke fun at you. She's quick at repartee. She's quite a back number. She's staying with me. She's the limit. She's very particular. She's weak in French. Skiddoo! Slow down! Spare his blushes! Spit it out! Stick to facts! Stow it! Such a coward! Such is life! Such thoughts are alien to him. Such was not my intention. Take it or leave it! Take my advice! Take the lot! Tell that to the marines. Tell us another! Thanks for calling! That applies to you;too. That beats everything! That beats me. That cuts no ice with me. That cuts no ice. That doesn't agree with me. That doesn't mean a lot. That fact escaped me. That goes without saying. That needs character. That remains to be seen. That speaks in his favour. That takes just as long. That was a close shave. That was not aimed at you. That was the last straw. That will do. That will get you nowhere. That won't hurt. That won't my case. That won't wash with me. That's a bit fishy. That's a bit thick. That's a feather in his cap. That's a live wire. That's a matter of argument. That's a matter of opinion. That's a minor matter. That's a pretty kettle of fish. That's a pretty mess! That's a solution of striking simplicity. That's a tall order. That's a wine for you! That's all my eye and Betty Martin. That's all part of my job. That's an absolute scream! That's an odd thing. That's another story. That's anyone's guess. That's beside the point. That's coming it strong. That's down my alley. That's going to far. That's half the battle. That's his blind side. That's just about the limit. That's just like you. That's just not done. That's mere child's play. That's my affair. That's news to me. That's no advantage to you! That's no comfort to me! That's none of your business. That's not half bad. That's not my pigeon. That's not to be sneezed. That's not worth mentioning. That's nothing to me. That's nothing to you. That's nothing! That's nuts to him. That's of no importance. That's over my head. That's saying a lot. That's splitting hairs. That's strictly forbidden! That's the absolute truth. That's the last straw! That's the limit. That's the snag! That's to deep for me. That's torn it! That's true;of course. That's very unlike him. That's what I call smart. That's why you're here. The ball's in your court. The coast is clear. The colours do not match. The day turned out to be a fine one. The disease is contagious. The dust has settled. The end is not far off. The engin runs by a battery. The fat is in the fire. The game is not worth the candle. The headline caught my eye this morning. The idea suggests itself. The line is busy. The line's gone dead. The machine runs on AC. The main parts were well cast. The money won't last. The moon is waning. The motion was defeated. The murder is out. The news is good. The news made me jump. The noes have it. The opportunity arises. The period runs. The picture is crooked. The plan failed. The proof of the pudding is in the eating. The question doesn't arise. The question is whether this is true. The rain passed off. The room faces north. The sense escapes me. The subject never came up. The sun is low. The train is already due. The train will be long in coming. The very idea! The weather keeps up. The whole thing is off. The whole thing looks fishy. The word was coined by Schiller. There fell a deep silence. There isn't much fear of it. There was a lot of laughter. There you are! There you're mistaken. There's a draught. There's a rub in it. There's an end of it! There's more behind. There's more to it than that. There's no denying. There's no hurry. There's no limit to his ambition. There's no love lost between them. There's no room for hope. There's room for improvement. There's something in the wind. There's still a lot to be done. There's the rub. These pills did me no good. They asked me point-black. They attend the meeting. They claimed their money back. They didn't get on well together. They feel empty. They got to be friends. They got up at an unearthly hour. They invited friend and foe. They sympathize with him. They were caught red-handed. They're a bad match. They're respectable people. This book makes good reading. This calls for a celebration. This concerns you. This is of my own making. This is on me. This is possible with him. This is something like! This is the limit! This isn't everybodys job. This may have serious consequences. This means you. This place is haunted. This reminds me of home. This won't do. This won't serve my turn. Those were the days. Though he's poor;he's an honest man. Times have changed. Trouble's brewing! Turn on the light! Unless I'm very much mistaken. Upon my Sam! Upon my life! Use it sparingly! Wait and see! Wait your turn! Walk inside! Was it worth while? We are near relatives. We are promised higher wages. We are running out of. We could think of nothing to say. We drew lots. We happened to see it. We made him talk. We made it up. We met at night. We must stuck together. We play for love. We shall not know until next year. We slept out of doors. We used to live in the country. We'll pool expenses and travel together. We're only pretending. We've got some good news. We've had a tough time. We've missed you badly. Well;I never! Wet paint! What a beauty! What a pity! What a shame! What a sight you are. What about breakfast ? What an object you're! What are his politics? What are you driving at ? What cheek! What considerations? What do you do in your spare time ? What do you make of it? What does he do for a living? What has come over you? What made him do that? What next? What relation is she to you? What remains to be done? What sense! What size do you take? What's all this good for? What's behind all this? What's he driving at? What's it all about ? What's on your mind? What's the fare ? What's the news? What's the occasion? What's the odds? What's the pitch? What's the score ? What's the use anyhow? What's wrong with you? What's your height? What's your opinion? Where are you bound for ? Where did we break off ? Where do you live? Where is your permanent residence? Who cares? Who is in favour? Who is your date? Who's ahead? Who's the brain behind? Whoever else? Will this glass do you? Will you kindly shut up! With knobs on! With tender and loving care she follows him. Worse luck! You are telling me! You can bet your bottom dollar. You can count me out! You can count on that. You can take it from me. You can talk. You can't fool me. You carry things too far. You don't say so! You don't say so. You don't say! You had better go. You have to make up your mind. You haven't an earthly. You look dishevelled. You look well. You may as well own up. You must apologize. You must be out of your mind. You needn't justify yourself. You never can tell. You never know. You ought to be ashamed of yourself. You ought to have known better. You'll hear of this! You're a brick! You're a pain in the neck. You're certain to need help. You're free to go. You're in a rut. You're probably aware of it. You're wasting your breath. You've a day off. You've asked for it. You've been had. You've got me there. You've got me wrong. You've much to answer for. You've my apologies. You've no idea! Your chances are high. On 4 June 1996, e.g. the maiden flight of the Ariane 5 launcher. Only about 40 seconds. Engineers from the Ariane 5 project teams of CNES and Industry immediately started to investigate the failure. Over the following days, the Director General of ESA and the Chairman of CNES set up an independent Inquiry Board and nominated the following members. The terms of reference assigned to the Board requested it to determine the causes of the launch failure. The terms of reference assigned to the Board requested it to investigate whether the qualification tests and acceptance tests were appropriate in relation to the problem encountered. The terms of reference assigned to the Board requested it to recommend corrective action to remove the causes of the anomaly and other possible weaknesses of the systems found to be at fault. The Board started its work on 13 June 1996. It was assisted by a Technical Advisory Committee. Consequently, the recommendations made by the Board are limited to the areas examined. The report contains the analysis of the failure, the Board's conclusions and its recommendations for corrective measures, most of which should be undertaken before the next flight of Ariane 5. There is in addition a report for restricted circulation in which the Board's findings are documented in greater technical detail. Although it consulted the telemetry data recorded during the flight, the Board has not undertaken an evaluation of those data. Nor has it made a complete review of the whole launcher and all its systems. This report is the result of a collective effort by the Commission, assisted by the members of the Technical Advisory Committee. We have all worked hard to present a very precise explanation of the reasons for the failure and to make a contribution towards the improvement of Ariane 5 software. This improvement is necessary to ensure the success of the programme. On the basis of the documentation made available and the information presented to the Board, the following has been observed. The weather at the launch site at Kourou on the morning of 4 June 1996 was acceptable for a launch that day, and presented no obstacle to the transfer of the launcher to the launch pad. In particular, there was no risk of lightning since the strength of the electric field measured at the launch site was negligible. The only uncertainty concerned fulfilment of the visibility criteria. The countdown, which also comprises the filling of the core stage, went smoothly until H0-7 minutes when the launch was put on hold since the visibility criteria were not met at the opening of the launch window (08h35 local time). Visibility conditions improved as forecast and the launch was initiated at H0 = 09h 33mn 59s local time (=12h 33mn 59s UT). Ignition of the Vulcain engine and the two solid boosters was nominal, as was lift-off. The vehicle performed a nominal flight until approximately H0 + 37 seconds. Shortly after that time, it suddenly veered off its flight path, broke up, and exploded. A preliminary investigation of flight data showed. Self-destruction of the launcher correctly triggered by rupture of the links between the solid boosters and the core stage. The origin of the failure was thus rapidly narrowed down to the flight control system and more particularly to the Inertial Reference Systems, which obviously ceased to function almost simultaneously at around H0 + 36.7 seconds. The information available on the launch includes telemetry data received on the ground until H0 + 42 seconds. The information available on the launch includes trajectory data from radar stations. The information available on the launch includes optical observations (IR camera, films) - inspection of recovered material. CNES provided a copy of the data to Aerospatiale, which carried out analyses concentrating mainly on the data concerning the electrical system. The self-destruction of the launcher occurred near to the launch pad, at an altitude of approximately 4000 m. Therefore, all the launcher debris fell back onto the ground, scattered over an area of approximately 12 km2 east of the launch pad. Recovery of material proved difficult, however, since this area is nearly all mangrove swamp or savanna. Nevertheless, it was possible to retrieve from the debris the two Inertial Reference Systems. The results of the examination of this unit were very helpful to the analysis of the failure sequence. Post-flight analysis of telemetry has shown a number of anomalies which have been reported to the Board. They are mostly of minor significance and such as to be expected on a demonstration flight. One anomaly which was brought to the particular attention of the Board was the gradual development, starting at Ho + 22 seconds, of variations in the hydraulic pressure of the actuators of the main engine nozzle. These variations had a frequency of approximately 10 Hz. There are some preliminary explanations as to the cause of these variations, which are now under investigation. After consideration, the Board has formed the opinion that this anomaly, while significant, has no bearing on the failure of Ariane 501. In general terms, the Flight Control System of the Ariane 5 is of a standard design. The attitude of the launcher and its movements in space are measured by an Inertial Reference System (SRI). It has its own internal computer, in which angles and velocities are calculated on the basis of information from a strap-down inertial platform, with laser gyros and accelerometers. In order to improve reliability there is considerable redundancy at equipment level. There are two SRIs operating in parallel, with identical hardware and software. One SRI is active and one is in hot stand-by, and if the OBC detects that the active SRI has failed it immediately switches to the other one, provided that this unit is functioning properly. Likewise there are two OBCs, and a number of other units in the Flight Control System are also duplicated. The design of the Ariane 5 SRI is practically the same as that of an SRI which is presently used on Ariane 4, particularly as regards the software. This angle of attack was caused by full nozzle deflections of the solid boosters and the Vulcain main engine. These nozzle deflections were commanded by the On-Board Computer (OBC) software on the basis of data transmitted by the active Inertial Reference System (SRI 2). Part of these data at that time did not contain proper flight data, but showed a diagnostic bit pattern of the computer of the SRI 2, which was interpreted as flight data. The reason why the active SRI 2 did not send correct attitude data was that the unit had declared a failure due to a software exception. The OBC could not switch to the back-up SRI 1 because that unit had already ceased to function during the previous data cycle (72 milliseconds period) for the same reason as SRI 2. The internal SRI software exception was caused during execution of a data conversion from 64-bit floating point to 16-bit signed integer value. The floating point number which was converted had a value greater than what could be represented by a 16-bit signed integer. This resulted in an Operand Error. The data conversion instructions (in Ada code) were not protected from causing an Operand Error, although other conversions of comparable variables in the same place in the code were protected. The error occurred in a part of the software that only performs alignment of the strap-down inertial platform. This software module computes meaningful results only before lift-off. As soon as the launcher lifts off, this function serves no purpose. The alignment function is operative for 50 seconds after starting of the Flight Mode of the SRIs which occurs at H0 - 3 seconds for Ariane 5. Consequently, when lift-off occurs, the function continues for approx. 40 seconds of flight. This time sequence is based on a requirement of Ariane 4 and is not required for Ariane 5. The Operand Error occurred due to an unexpected high value of an internal alignment function result called BH, Horizontal Bias, related to the horizontal velocity sensed by the platform. This value is calculated as an indicator for alignment precision over time. The value of BH was much higher than expected because the early part of the trajectory of Ariane 5 differs from that of Ariane 4 and results in considerably higher horizontal velocity values. The SRI internal events that led to the failure have been reproduced by simulation calculations. Furthermore, both SRIs were recovered during the Board's investigation and the failure context was precisely determined from memory readouts. In addition, the Board has examined the software code which was shown to be consistent with the failure scenario. The results of these examinations are documented in the Technical Report. Therefore, it is established beyond reasonable doubt that the chain of events set out above reflects the technical causes of the failure of Ariane 501. In the failure scenario, the primary technical causes are the Operand Error when converting the horizontal bias variable BH, and the lack of protection of this conversion which caused the SRI computer to stop. It has been stated to the Board that not all the conversions were protected because a maximum workload target of 80% had been set for the SRI computer. To determine the vulnerability of unprotected code, an analysis was performed on every operation which could give rise to an exception, including an Operand Error. In particular, the conversion of floating point values to integers was analysed and operations involving seven variables were at risk of leading to an Operand Error. This led to protection being added to four of the variables, evidence of which appears in the Ada code. However, three of the variables were left unprotected. No reference to justification of this decision was found directly in the source code. Given the large amount of documentation associated with any industrial application, the assumption, although agreed, was essentially obscured, though not deliberately, from any external review. It is important to note that the decision to protect certain variables but not others was taken jointly by project partners at several contractual levels. Although the source of the Operand Error has been identified, this in itself did not cause the mission to fail. The specification of the exception-handling mechanism also contributed to the failure. It was the decision to cease the processor operation which finally proved fatal. Restart is not feasible since attitude is too difficult to re-calculate after a processor shutdown; therefore the Inertial Reference System becomes useless. The reason behind this drastic action lies in the culture within the Ariane programme of only addressing random hardware failures. From this point of view exception - or error - handling mechanisms are designed for a random hardware failure which can quite rationally be handled by a backup system. Although the failure was due to a systematic software design error, mechanisms can be introduced to mitigate this type of problem. For example the computers within the SRIs could have continued to provide their best estimates of the required attitude information. There is reason for concern that a software exception should be allowed, or even required, to cause a processor to halt while handling mission-critical equipment. Indeed, the loss of a proper software function is hazardous because the same software runs in both SRI units. In the case of Ariane 501, this resulted in the switch-off of two still healthy critical units of equipment. The period selected for this continued alignment operation, 50 seconds after the start of flight mode, was based on the time needed for the ground equipment to resume full control of the launcher in the event of a hold. This special feature made it possible with the earlier versions of Ariane, to restart the count- down without waiting for normal alignment, which takes 45 minutes or more, so that a short launch window could still be used. In fact, this feature was used once, in 1989 on Flight 33. Even in those cases where the requirement is found to be still valid, it is questionable for the alignment function to be operating after the launcher has lifted off. Alignment of mechanical and laser strap-down platforms involves complex mathematical filter functions to properly align the x-axis to the gravity axis and to find north direction from Earth rotation sensing. The assumption of preflight alignment is that the launcher is positioned at a known and fixed position. Therefore, the alignment function is totally disrupted when performed during flight, because the measured movements of the launcher are interpreted as sensor offsets and other coefficients characterising sensor behaviour. Returning to the software error, the Board wishes to point out that software is an expression of a highly detailed design and does not fail in the same sense as a mechanical system. Furthermore software is flexible and expressive and thus encourages highly demanding requirements, which in turn lead to complex implementations which are difficult to assess. An underlying theme in the development of Ariane 5 is the bias towards the mitigation of random failure. The supplier of the SRI was only following the specification given to it, which stipulated that in the event of any detected exception the processor was to be stopped. The exception which occurred was not due to random failure but a design error. The exception was detected, but inappropriately handled because the view had been taken that software should be considered correct until it is shown to be at fault. The Board has reason to believe that this view is also accepted in other areas of Ariane 5 software design. The Board is in favour of the opposite view, that software should be assumed to be faulty until applying the currently accepted best practice methods can demonstrate that it is correct. The Flight Control System qualification for Ariane 5 follows a standard procedure and is performed at the following levels. The logic applied is to check at each level what could not be achieved at the previous level, thus eventually providing complete test coverage of each sub-system and of the integrated system. Testing at equipment level was in the case of the SRI conducted rigorously with regard to all environmental factors and in fact beyond what was expected for Ariane 5. However, no test was performed to verify that the SRI would behave correctly when being subjected to the count-down and flight time sequence and the trajectory of Ariane 5. Had such a test been performed by the supplier or as part of the acceptance test, the failure mechanism would have been exposed. The Board has also noted that the systems specification of the SRI does not indicate operational restrictions that emerge from the chosen implementation. Such a declaration of limitation, which should be mandatory for every mission-critical device, would have served to identify any non-compliance with the trajectory of Ariane 5. The other principal opportunity to detect the failure mechanism beforehand was during the numerous tests and simulations carried out at the Functional Simulation Facility ISF, which is at the site of the Industrial Architect. The scope of the ISF testing is to qualify the guidance, navigation and control performance in the whole flight envelope. A large number of closed-loop simulations of the complete flight simulating In these tests many equipment items were physically present and exercised but not the two SRIs, which were simulated by specifically developed software modules. Some open-loop tests, to verify compliance of the On-Board Computer and the SRI, were performed with the actual SRI. It is understood that these were just electrical integration tests and low-level (bus communication) compliance tests. It is not mandatory, even if preferable, that all the parts of the subsystem are present in all the tests at a given level. Sometimes this is not physically possible or it is not possible to exercise them completely or in a representative way. In these cases it is logical to replace them with simulators but only after a careful check that the previous test levels have covered the scope completely. In order to understand the explanations given for the decision not to have the SRIs in the closed-loop simulation, it is necessary to describe the test configurations that might have been used. This is similar to the method mentioned in connection with possible testing at equipment level. To substitute both, the analog output of the accelerometers and the Ring Laser Gyros via a dedicated test input connector with signals produced by simulation. In both cases a large part of the electronics and the complete software are tested in the real operating environment. When the project test philosophy was defined, the importance of having the SRIs in the loop was recognized and a decision was taken to select method B above. At a later stage of the programme (in 1992), this decision was changed. It was decided not to have the actual SRIs in the loop. The precision of the navigation software in the On-Board Computer depends critically on the precision of the SRI measurements. In the ISF, this precision could not be achieved by the electronics creating the test signals. The simulation of failure modes is not possible with real equipment, but only with a model. The base period of the SRI is 1 millisecond whilst that of the simulation at the ISF is 6 milliseconds. This adds to the complexity of the interfacing electronics and may further reduce the precision of the simulation. While high accuracy of a simulation is desirable, in the ISF system tests it is clearly better to compromise on accuracy but achieve all other objectives, amongst them to prove the proper system integration of equipment such as the SRI. The precision of the guidance system can be effectively demonstrated by analysis and computer simulation. Nevertheless, it is evident that the limitations of the SRI software were not fully analysed in the reviews, and it was not realised that the test coverage was inadequate to expose such limitations. Nor were the possible implications of allowing the alignment software to operate during flight realised. In these respects, the review process was a contributory factor in the failure. In accordance with its termes of reference, the Board has examined possible other weaknesses, primarily in the Flight Control System. No weaknesses were found which were related to the failure, but in spite of the short time available, the Board has conducted an extensive review of the Flight Control System based on experience gained during the failure analysis. In addition, the Board has made an analysis of methods applied in the development programme, in particular as regards software development methodology. The results of these efforts have been documented in the Technical Report and it is the hope of the Board that they will contribute to further improvement of the Ariane 5 Flight Control System and its software. During the launch preparation campaign and the count-down no events occurred which were related to the failure. The meteorological conditions at the time of the launch were acceptable and did not play any part in the failure. No other external factors have been found to be of relevance. Engine ignition and lift-off were essentially nominal and the environmental effects (noise and vibration) on the launcher and the payload were not found to be relevant to the failure. Propulsion performance was within specification. 22 seconds after H0 (command for main cryogenic engine ignition), variations of 10 Hz frequency started to appear in the hydraulic pressure of the actuators which control the nozzle of the main engine. This phenomenon is significant and has not yet been fully explained, but after consideration it has not been found relevant to the failure. At 36.7 seconds after H0 (approx. 30 seconds after lift-off) the computer within the back-up inertial reference system, which was working on stand-by for guidance and attitude control, became inoperative. This was caused by an internal variable related to the horizontal velocity of the launcher exceeding a limit which existed in the software of this computer. 5 seconds later the active inertial reference system, identical to the back-up system in hardware and software, failed for the same reason. Since the back-up inertial system was already inoperative, correct guidance and attitude information could no longer be obtained and loss of the mission was inevitable. As a result of its failure, the active inertial reference system transmitted essentially diagnostic information to the launcher's main computer, where it was interpreted as flight data and used for flight control calculations. On the basis of those calculations the main computer commanded the booster nozzles, and somewhat later the main engine nozzle also, to make a large correction for an attitude deviation that had not occurred. A rapid change of attitude occurred which caused the launcher to disintegrate at 39 seconds after H0 due to aerodynamic forces. Destruction was automatically initiated upon disintegration, as designed, at an altitude of 4 km and a distance of 1 km from the launch pad. The debris was spread over an area of 5 x 2.5 km2. Amongst the equipment recovered were the two inertial reference systems. They have been used for analysis. The post-flight analysis of telemetry data has listed a number of additional anomalies which are being investigated but are not considered significant to the failure. The inertial reference system of Ariane 5 is essentially common to a system which is presently flying on Ariane 4. This realignment function, which does not serve any purpose on Ariane 5, was nevertheless retained for commonality reasons and allowed, as in Ariane 4, to operate for approx. 40 seconds after lift-off. When taking this design decision, it was not analysed or fully understood which values this particular variable might assume when the alignment software was allowed to operate after lift-off. Ariane 5 has a high initial acceleration and a trajectory which leads to a build-up of horizontal velocity which is five times more rapid than for Ariane 4. The higher horizontal velocity of Ariane 5 generated, within the 40-second timeframe, the excessive value which caused the inertial system computers to cease operation. The purpose of the review process, which involves all major partners in the Ariane 5 programme, is to validate design decisions and to obtain flight qualification. In this process, the limitations of the alignment software were not fully analysed and the possible implications of allowing it to continue to function during flight were not realised. The specification of the inertial reference system and the tests performed at equipment level did not specifically include the Ariane 5 trajectory data. Consequently the realignment function was not tested under simulated Ariane 5 flight conditions, and the design error was not discovered. It would have been technically feasible to include almost the entire inertial reference system in the overall system simulations which were performed. For a number of reasons it was decided to use the simulated output of the inertial reference system, not the system itself or its detailed simulation. Had the system been included, the failure could have been detected. Post-flight simulations have been carried out on a computer with software of the inertial reference system and with a simulated environment, including the actual trajectory data from the Ariane 501 flight. These simulations have faithfully reproduced the chain of events leading to the failure of the inertial reference systems. The failure of the Ariane 501 was caused by the complete loss of guidance and attitude information 37 seconds after start of the main engine ignition sequence (30 seconds after lift- off). This loss of information was due to specification and design errors in the software of the inertial reference system. On the basis of its analyses and conclusions, the Board makes the following recommendations. Switch off the alignment function of the inertial reference system immediately after lift-off. More generally, no software function should run during flight unless it is needed. Prepare a test facility including as much real equipment as technically feasible, inject realistic input data, and perform complete, closed-loop, system testing. Complete simulations must take place before any mission. A high test coverage has to be obtained. Do not allow any sensor, such as the inertial reference system, to stop sending best effort data. Organize, for each item of equipment incorporating software, a specific software qualification review. The Industrial Architect shall take part in these reviews and report on complete system testing performed with the equipment. All restrictions on use of the equipment shall be made explicit for the Review Board. Make all critical software a Configuration Controlled Item (CCI). Review all flight software (including embedded software). Identify all implicit assumptions made by the code and its justification documents on the values of quantities provided by the equipment. Check these assumptions against the restrictions on use of the equipment. Verify the range of values taken by any internal or communication variables in the software. Wherever technically feasible, consider confining exceptions to tasks and devise backup capabilities. Provide more data to the telemetry upon failure of any component, so that recovering equipment will be less essential. Reconsider the definition of critical components, taking failures of software origin into account (particularly single point failures). Include external (to the project) participants when reviewing specifications, code and justification documents. Make sure that these reviews consider the substance of arguments, rather than check that verifications have been made. Include trajectory data in specifications and test requirements. Review the test coverage of existing equipment and extend it where it is deemed necessary. Give the justification documents the same attention as code. Improve the technique for keeping code and its justifications consistent. Including external RAMS experts is to be considered. A more transparent organisation of the cooperation among the partners in the Ariane 5 programme must be considered. Close engineering cooperation, with clear cut authority and responsibility, is needed to achieve system coherence, with simple and clear interfaces between partners. Microsoft is not alone in forging the future of NT and its other Windows products. In this first offering in a series of in-depth analyses called The Next 10 Minutes, join Nicholas Petreley as he examines some of the forces leaving their marks on the evolving Windows NT. This article is the first in a series that will explore the future of Windows, particularly Windows NT, with respect to the network-centric computing market forces that are shaping Microsoft's long-term strategies. Microsoft is engaging in an all-out war against platform-neutral network-centric computing. This war has a direct effect on the Windows product line and, as Mail this we hope to demonstrate, Windows article to NT is affected the most. As a friend Microsoft forges the future Windows NT, it is also engaged in warding off competition, protecting its dominance, and fighting this war against platform-neutral, network-centric computing, among other things. The design and marketing of Windows and its related products will certainly be affected by these multiple and sometimes conflicting forces. The shot heard around the world Though it is tempting to attribute the animosity that exists between Microsoft and the network computer to Bill Gates, let us not forget that Larry Ellison started this fight. Ellison challenged Microsoft's monopoly on the desktop, and Microsoft responded. It's understandable that Gates is often criticized more heavily for his response than Ellison for his challenge. Compared to Gates, Ellison is the underdog. Gates has the monopoly, the cash cow, and a reputation for protecting both at all costs, even if it means attracting allegations of anti-competitive and illegal behavior. But Bill Gates did not pick this fight. That's an important distinction. The company that commands a monopoly is often seen as the company that holds all the cards and therefore controls the future. And it is therefore logical that this company has all the advantages and none of the disadvantages when it's challenged. That's why, in the recent senate hearings regarding Microsoft, Scott McNealy and Jim Barksdale repeatedly pointed out that the law must (and does) impose a different set of rules on a monopoly than it does on the competition. But it isn't entirely true that Microsoft has all the advantages, depending on how Microsoft decides to respond to any given challenge. Almost everyone but Bill Gates seemed to agree during the senate hearings that Microsoft's power puts it in a unique position in this industry. But few mentioned that it can be a uniquely difficult position. Unlike its competition, Microsoft bears the overwhelming burden of protecting its monopoly at all costs. It is difficult to describe just how heavy this burden must weigh on the shoulders of Microsoft management. One might imagine it is similar to that of a person bound by his or her pride, determined to maintain a lie while being challenged by several opponents who continually threaten to expose the truth. Anyone who has tried this knows how stressful it is to fine-tune your alibi on a continual basis while trying to remember everything you've said in the past to prevent inconsistencies. This analogy seems quite apt because it describes a feeling of paranoia and smothering pressure (characteristics often attributed to Microsoft and its management). The two are also similar in that the prideful liar and reactive monopolist both have the opportunity to release their respective burdens without putting themselves in jeopardy. The liar can confess the truth, and the monopolist can choose to risk losing control for the sake of building the right products for its customer. But they both tend to refuse. Both corrective actions require a sense of humility. By definition, the person bound by pride lacks this quality. And while I gladly credit the management at Microsoft with intelligence and numerous admirable qualities, the quality of humility is rarely to be found among them. Staged humility for the benefit of the press does not drive the company's strategic decisions. So it's entirely ineffective at relieving Microsoft's burden. Pavlov's dogged monopoly This burden gives rise to self-imposed market forces that direct Microsoft's product design and marketing strategies. Even the casual observer can see that Microsoft protects its monopoly by. Leveraging its existing desktop monopoly to ward off direct threats from competitive products and technologies. Leveraging its existing desktop monopoly to enter and dominate new markets that may ultimately threaten said existing desktop monopoly. Investing in whatever means necessary to keep the U.S. Department of Justice and other legal challenges under control while pursuing goals 1 and 2. This brings us to the question: Does Microsoft consider network-centric, platform-neutral computing threatening enough to change its product goals and strategies, as modified by the above priorities? To read most of the trade journals, Java-based network computers (the soul of platform-neutral computing) don't have a chance. If Microsoft is threatened by them in spite of this, there is an easy way to find out. If the network computer amounted to nothing more than an attempt to dethrone Bill Gates, it would fully deserve a quick death. But Microsoft demonstrated right from the start that it understood the value of platform-neutral, network-centric computing by launching a full-scale misinformation war to discredit the idea. Remember, Microsoft wrote off the Internet as a fad a year before it was ready with a browser product. Now Microsoft boasts nearly a 50 percent browser marketshare. While Bill Gates was ridiculing the idea of live multimedia over the Internet in an InfoWorld interview, Microsoft was investing in companies developing that very technology. The pattern is undeniable. Therefore, judging from the lengths to which Microsoft will go to damage the credibility of the network computer, one could easily conclude that Microsoft thinks the network computer is a marvelous idea. Which is why it has redirected its entire Windows strategy to battle it. First, Microsoft promised the NetPC and Zero Administration Windows. After a brief flurry of praise from the press, these concepts are all but off the radar screen as of the moment. The Windows everywhere plan would have to be rewritten from scratch. Meanwhile, Microsoft has complicated its plans for Windows NT by turning it into a multiuser system to support thin clients, its second line of defense against the network computer. In order to accomplish this, Microsoft has long-term plans to re-engineer Windows CE from a consumer device operating system into a thin-client OS. All of these shifts in product strategy resulted directly from Microsoft's need to combat the threat of platform-neutral, network-centric computing to its desktop monopoly. But there's another threat on the horizon. It's called Unix. Next month, we'll observe how Microsoft is reacting to the threat of Unix, and how this reaction is shaping Microsoft's approach to Windows NT. Nicholas Petreley is editor-in-chief of NC World and columnist for InfoWorld and NT World Japan. Read Nicholas' column Down to the Wire in InfoWorld. Reach Nicholas at nicholas.petreley@ncworldmag.com. This document may be freely distributed in its original form, provided no fee is charged and nothing is edited, deleted or otherwise modified. Distribution for profit, in whole or in part, without the express written consent of the author, is strictly prohibited. Real World Industries gives no warranty, expressed or implied, as to the suitability of this information to your particular system. Every effort has been taken to ensure that the information is as accurate as possible at the time of this writing. Real World Industries is not responsible for any damage caused by use or misuse of the information contained in this article. The price of memory today is at an all time low, with profits being squeezed all the way down to the manufacturer. Gone are the days of 40, 30 or even 20% profits, in many cases, for either the manufacturer or the reseller. As a result, a number of schemes have been developed by unscrupulous people in the industry to cut the costs, and they are taking advantage of an unsuspecting public. Among these schemes are usage of low-grade chips and fake parity, poor manufacturing and QA processes, remanufacturing or reselling of used memory and remarking of chips. This article will attempt to provide the information necessary to allow people to make an informed decision when purchasing computer memory. Some of the information is highly technical in nature, and is provide only for those interested in some of the 'inner workings' of the memory they are purchasing. The focus of the article is on Dynamic RAM (DRAM) memory, since that is the type the majority of systems use today. DRAM needs to be refreshed frequently or the data is lost, which is the reason it is called 'dynamic', and is relatively inexpensive to manufacture compared to SRAM. The most common DRAM modules in use today are Fast Page Mode (FPM) and Extended Data Out (EDO), which describes the method in which the data is accessed and made available to the CPU. More specialized DRAM memory is Burst EDO (BEDO), Synchronous DRAM (SDRAM),Video RAM (VRAM), Window RAM (WRAM), Synchronous Graphics RAM (SGRAM), and RAMBUS RAM. Since there is not a great deal of demand for the more specialized memory yet, , the 'scammers' do not appear to have infested that segment of the market, yet. Memory types that are not covered here include Static RAM (SRAM) and Read Only Memory (ROM). SRAM does not need to be refreshed as long as power is applied, and is used primarily for cache. ROM is used mainly for the BIOS, where the data must remain intact even with no power applied. ROM memory also includes PROM, EPROM, EEPROM and FLASH memory. EEPROM and FLASH memory are used in systems where the BIOS can be 'upgraded' using a utility and data provided by the BIOS manufacturer. The second and third section of the article focuses on technical details of memory chips, circuit boards and terminology. The fourth section discusses how the memory chips are made into modules, and some various module designs. Section five discusses the memory market, and how memory is distributed and sold, and some of the major players. Those who do not wish to delve into technical details may want to skip directly to section six, 'Memory Buying Considerations', which gives the information on how to identify the frauds, at least as much as is possible. The final section (seven) is a brief wrap-up and summary of the article, and the Appendix lists chip and module manufacturers with the URL of their Web site. If you know of others, please let me know so I can keep the list updated. These sites are invaluable for being able to identify specific chips and for technical articles, as well as new products. DRAM chips are produced in three forms: DIP (dual in-line package), SOJ (small outline J-lead) and TSOP (thin, small outline package). The DIP package has leads extending straight down on both sides of the chip, and were designed to be inserted into small holes in the printed circuit board and soldered into place. The DIP style was originally used for mounting memory directly onto the mainboard. Today, they are primarily used for level 2 cache and are inserted into sockets rather than soldered. SOJs are similar to DIPs, but their leads are bent under at the end, thus giving them the name 'J-lead'. The TSOP chips are extremely thin (typically only a few millimeters thick) with the leads sort of 'splayed' out to the side. SOJ and TSOP packages are designed to be soldered onto the surface of a printed circuit board (called 'surface' mounting). Some video boards have sockets specifically designed for SOJ chips to be inserted. The semiconductor manufacturers produce these chips in large assembly plants, and in huge quantities. When an assembly line first starts producing chips, many of the chips will not perform as expected, and must be discarded or re-classified. As the line 'matures', these defects and sub-standard chips become fewer and fewer. Eventually, the equipment begins to wear out, and the defect rate starts to rise again. At this time, the line must be retooled for a new run. Each chip must be tested for reliability and speed as it comes off of the assembly line, to ensure the specifications have been met. Although a chip may be marked as a specific speed, it may actually perform faster. For example, a 60ns chip may actually run at 59ns, or even 50ns. If the tests indicate that the chip only runs at 61ns or 65ns, it will be marked as a 70ns chip. Chips that pass all of the reliability tests will be classified as A-grade (regardless of speed), while those that have some minor defects will be classified as C-grade. Any chips that are seriously defective will usually be destroyed. A-Grade chips are the most reliable, and are therefore considered the highest quality. These are also the most expensive chips, because of their desireability for most applications. C-Grade chips are less expensive, and are intended to be used primarily for devices that are not as demanding as today's computers and which do not have the exacting requirements, such as calculators and pagers. Some manufacturers have additional grades that they identify, and may use different markings to identify them. The chip manufacturer imprints a 'code' on each IC which indicates manufacturer name, chip configuration, speed rating and date of manufacture. This marking is put onto the IC in such a way that it is embedded into the plastic resin coating of the chip. The only way to remove this marking is to sand it off, or etch it off. After putting the markings on the IC, a protective coating is placed on the chip, giving it a somewhat reflective appearance. In addition, many manufacturers stamp small, recessed polished 'dots' into the top of the plastic jacket. This is done both to locate specific pins (such as pin 1) and to help identify non-factory remarks (see section six for more details on remarking). Manufacturers usually use a different marking for different grade chips. For example, Micron apparently uses their MT logo for their A-grade chips, while lesser grade chips are marked either 'USA' or 'Laser', depending upon how far below spec the chip is. Other manufacturers also use a generic 'country code' for their lower grade chips, so you may see modules with chips that merely say 'japan', 'france', 'korea', etc. If you do see these markings, you can be fairly certain that the module was made with sub-standard, inexpensive materials. In addition, manufacturers may have different 'lots' of chips with different prices, depending on how much QA has been performed. For example, Micron publishes a document which indicates there are four different pricing structures for chips. The highest priced chips have been very carefully tested, and are guaranteed to have less than a.1% failure rate. The lowest price chips have not been tested for reliablity or speed, so the buyer is getting chips 'as is', and may experience a relatively high failure rate. It is the responsibility of the buyer to test these chips to be sure they don't use marginal, substandard or non-functioning chips, which is why they are the least expensive. One must wonder how thoroughly a small 'generic' manufacturer tests (or has the ability to test) these chips before using them on a module. The chips come in varying sizes, such as 1 Megabit (abbreviated as Mb), 4Mb, 16Mb and the newer 64Mb. The chip contains 'cells' which hold the bits of data, and each cell may contain from 1 to 16 bits. For example, a 16Mb chip may be configured as 4Mbx4, 2Mbx8 or 1Mbx16, but in all cases the total capacity of the chip is 16Mb. The total number of cells per chip is indicated by the first number, and the number of bits per cell by the second number. The cells in the chip are arranged into a two dimensional array, and are accessed using a row and column number. Every column contains a Sense Amplifier, column select and precharge circuitry. During a read operation, every bit in the selected row is moved to it's respective Sense Amp. Since the cells in a DRAM chip will lose their 'memory' fairly quickly, the cells must be refreshed on a regular and frequent basis. This is called the refresh process (obviously), and the number of rows that must be refreshed at one time is called the refresh rate. The two most common refresh rates are 2K and 4K. The chips which have a 2K refresh rate can refresh more cells at a time than the 4K chips, and complete the process faster. This means that chips with a 2K refresh rate use less power. This reduces the overhead necessary for refreshing the chip. Several lines are used to signal when a row or column is to be accessed, which row or address is being accessed, and when the data is to be sent or received. The RAS and CAS lines signal when a row or column is to be accessed. The speed of a chip is typically measured in nanoseconds (ns) for asynchronous RAM. This speed indicates how quickly the data is made available from the time RAS falls to data output. The most common speeds today are 70ns and 60ns, with some faster DRAMs making their way into the market. Synchronous RAM (such as SDRAM) uses the mainboard clock for timing, and therefore is rated in MHz rather than nanoseconds. Fast Page Mode (FPM) memory is currently the most common type of DRAM used in microcomputers, but it is quickly being overtaken by Extended Data Out (EDO) memory. This is because EDO makes the data available to the CPU faster, thereby increasing performance by as much as 60% over FPM (see caveats in the 'Memory Buying Considerations' below). FPM memory accesses occur as follows (this example is for a read operation). When the RAS line falls (low voltage), the row address contained in the address buffer is signaled, causing the bits to be moved to the Sense Amps. This is followed by the column address being signaled as CAS falls. The DOUT line is then turned on, indicating that the data is available. To access another column in the same row, the CAS line is cycled while changing the column address but leaving RAS low (this is Fast Page Mode). Each time CAS goes high, the DOUT line is turned off, disabling data transfer. EDO memory uses the same process for RAS and CAS, but the DOUT line is not turned off when CAS goes high, so a new access can begin at the next column address while data is being transferred from the current column. This allows for a faster page cycle time, thereby increasing performance. BEDO memory (Burst EDO) was developed by Micron Technology in the attempt to produce even faster memory. As it turnes out, this technology never really got off the ground, as SDRAM was considered technically better. This is because FPM, EDO and BEDO memory cannot handle bus speeds faster than 66MHz. While this is sufficient for the vast majority of motherboards today, it will be insufficient in the near future as faster bus speeds become popular. BEDO chips are currently used primarily for cache modules for video cards, etc. SDRAM (Synchronous DRAM) is one of the most exciting advancements in memory, right now. All operations in SDRAM are synchronized to the system clock signal. This eliminates the need to generate the analog RAS and CAS signals required for ansynchronous DRAM, which allows for faster performance. In addition, current SDRAM technology will handle bus speeds up to 125MHz. The implications of this will be discussed in the Memory Buying Considerations section. The PCBs used for a memory module are comprised of 'layers'. The signal, power and ground lines are sandwiched between these layers for protection, and to keep the lines separated. The standard PCB has four layers, but some module manufacturers (most notable are NEC, Samsung, Century, Unigen and Micron) use six layer boards. What is really most important in regards to the PCB is the design and materials used. For example, the 'typical' four layer board is designed with two signal layers on the outside, and the power and ground layers between them. This provides easy access to the signal layers in case repairs need to be performed. Unfortunately, this will not sufficiently protect against signal noise getting in or out. A better design is to have the two signal layers sandwiched between the power and ground layers, thus protecting the signals from outside noise, and preventing any 'internal' noise from affecting adjacent modules. The only way, as far as I know, to determine the quality of the PCB is to identify the manufacturer. You can then contact their customer service or sales department and have a chance of finding out. With generic modules you will probably never know if the manufacturer chose their PCBs based upon their design characteristics or their price. Most people think the memory modules they buy are produced by the large semiconductor manufacturers, such as Texas Instruments, Micron, NEC, Samsung, Toshiba, Motorola, etc., whose name appears on the chips. While this is true in some cases, there are also many memory module manufacturers who do not produce the semiconductors themselves. Instead, they purchase the components to produce the completed memory modules either directly from the manufacturer or from a third party supplier. In most cases the manufacturer will imprint or silk-screen their name onto the PCB or will place a sticker onto the board to identify themselves. This is true of the major, as well as the 3rd party manufacturers. Those that do not bear a name are most likely made by a 'generic' manufacturer. The large module manufacturers have contracts with the semiconductor manufacturers to purchase A-grade chips. Usually, the name of the IC manufacturer remains on the chip, but sometimes, through special arrangements, the IC manufacturer may imprint the name of the memory module manufacturer on the chip instead. This is a 'factory remark', and does not have any bearing on the quality of the chip. Memory modules come as either, SIPPs (Single In-line Pin Package), SIMMs (Single In-line Memory Module), DIMMs (Dual In-line Memory Module), or SO DIMMs (Small Outline DIMM). By far the most common today is SIMM memory, although DIMMs may be used on some Pentium motherboards, and SO DIMMs are used in laptops and notebooks. The 'leads' or pins may be either gold coated or tin, the selection of which is generally determined by the material used in the memory slot. For best results, gold lead modules should be used in memory slots with gold connectors, while tin leads should be matched with tin slots. SIMMs come in two main varieties: 30-pin or 72-pin, which indicates the number of lead connections. 30-pin SIMMs are 9 bits wide (8 data bits and a parity bit), while 72 pin SIMMs are 32 bits wide (non-parity) or 36 bits wide (parity). Since the 386 and 486 processors use a 32 bit (4 byte) wide memory bus, you need to use four 30-pin SIMMs at a time, whereas you only need one 72 pin SIMM. Pentium systems have a 64 bit (8 byte) memory bus, so SIMMs must be used in pairs (Pentiums only use 72-pin SIMMs), or a single DIMM may be used (DIMMs are 64 bits wide and have 168 pin connectors). The number of memory modules necessary to 'fill up' the bus is called a 'bank' of memory. Parity checking is a method of 'adding up' the 8 data bits at write time (actually just the zeros or ones, depending on the type of parity checking), and storing the result (either a 0 or a 1) in the parity bit. At read time, the data bits are again 'added up', and the result compared to the stored parity bit. If the result matches, then the data is assumed to be unchanged, so that date integrity is somewhat assured. This type of checking can detect (but not correct) a single bit error - however, a two bit error would go unnoticed, and the generated parity would match what was stored. Estimates given say that with today's memory technology, a single-bit data error occurs once every ten years (on any given module) - however, when they occur, any number of things may happen. Depending on the nature of the application, parity checking may or may not be considered important. Even better checking is available with ECC (error correction checking), which uses 7 or 8 bits as a parity check (depending on whether the processor has a 4 or 8 byte memory bus). This allows a single bit error to be not only detected, but corrected, and allows 2, 3 and 4 bit errors to be detected. Since experience shows that 98% of all data errors are single-bit, this level of parity checking can usually only be cost justified in very critical applications. You may see 30-pin modules advertised as 1x9 or 4x9, which designates the number of bits being transferred at a time (including the parity bit), while 72 pin will be indicated as 1x32 or 1x36 (for a 4MB non-parity or parity module). Most motherboards today can take either parity or non-parity 72 pin memory modules. Those that take 30-pin modules must be use parity (Apple computers used non-parity 30 pin SIMMs, but IBM PCs cannot). The number of chips on each module depends upon the chip size and the module capacity. For example, it would take 32Mb to make a 4MB RAM module (8 bits make a byte, so divide the total number of megabits by 8). Therefore, a 4MB module might have eight 4Mb chips or two 16Mb chips. With larger chips becoming available (such as the more recent 64Mb chip), larger memory modules can be made available without 'overloading' the board (larger than 32MB). 30-pin memory modules may be either 9-chip or 3-chip, depending on what size chip is used (1Mb or 4Mb). The third chip on a 3-chip module is the 1Mb parity chip. If too many chips are placed on a single module, overheating can result, which may damage the module. Apple coined the terms 'composite' and 'non-composite' to describe the smaller vs. Since a 72-pin SIMM is 32 bits wide, the total number of bit lines on a particular SIMM bank must equal 32. Sometimes to make a specific size module using chips of a 'standard' configuration, the resulting module has 64 bit lines. In that case, the module must be designed as a double-bank module. If we take an 8MB SIMM as an example (requiring 64Mb total) and figure different configurations, we come up with the following: Using four 16Mb chips (2Mb x 8 configuration), there is a total of 32 bit lines - requiring only a single bank. However, using four 16Mb chips (1Mb x 16 configuration), or using sixteen 4Mb chips (1Mb x 4 configuration), we have a total of 64 bit lines - requiring two banks! Note that four 16Mb chips in a 4Mb x 4 configuration won't work, because there are only 16 bit lines, but if we use eight of them, we have a single bank 16MB SIMM. Also, sixteen 4Mb chips in a 4Mb x 1 configuration also would not work on a 4MB module. Unfortunately, the single bank 8MB SIMMs need a TTL gate to 'emulate' two banks, which some motherboards won't recognize - which is why some 4 chip SIMMs won't work on every motherboard. As a result, all 8MB SIMMs are either 'true' double-bank or 'emulated' double-bank, but they are all double-bank (the same with 32MB SIMMs), to conform to standard memory module design. If this is not observed by the manufacturer, the module may not be usable in many systems. Since a manufacturing plant is extremely expensive and time consuming to build, this helps provide a guaranteed return on investment, and protects the manufacturer from fluctuations in the marketplace. The remaining chips are sold through distribution channels. The companies that buy the majority of the chips are memory module manufacturers, such as Kingston, Century, Unigen, Simple, Advantage, etc. Quality manufacturers will use only A-Grade chips to ensure reliability. This allows them to compete by selling their product at a lower price, but at the cost of reliability and performance. Manufacturing a completed memory module requires only a few components, which is a Printed Circuit Board, some chips (and some machinery for surface mounting the chips) and a knowledge of the pinouts of the chips. The quality of the components, and the manufacturing process will ultimately determine the quality of the memory module. Too much heat applied to a chip can reduce the reliability and even the speed of the chip, making it slower than the markings indicate. As stated previously, the chip speed is indicated along with the other markings. This is usually specified with either a minus sign (-) followed by a digit, or by the last one or two characters of the chip identifier. For example, the speed might be indicated as '-6' for a 60 nanosecond (ns) chip (or might be '-06' or '-60', or may be specified something like 'GM71C17400AJ6' where the last digit indicates the speed. The speed is an indicator of how long the memory chip takes to respond to the request from the CPU, with a lower number being faster. Generally speaking, the faster the bus speed, the faster the memory needs to be. For example, an 8MHz bus requires only about 150ns module, a 33MHz bus should have a 70ns module, and a 66MHz bus needs a 60ns for best performance. It is usually not a problem to have a memory module that is faster than what the CPU requires, but a slower chip can cause application errors and lockups. It is also a good idea to try to match the speeds of your SIMMs, especially on the same bank, but usually there are no undesirable side effects if they are all faster than what the CPU needs. Recall in the section on memory chips that the speed rating is only an indication of the slowest speed the chip may run at. It is theoretically possible (and, in fact, probable) that a given memory module could have one chip that performs at 52ns, another at 56ns and another at 60ns. KNOW WHAT YOU ARE BUYING - The first consideration is to determine who is the chip manufacturer and who is the module manufacturer. There are a number of chip manufacturers, and it is difficult to know all of them. A list of known chip and module manufacturers listed at the end of this article (I hope to include their symbols and chip codes in a future version of this article). There is no way to determine whether you are getting C-Grade chips without having a very good (and expensive) memory tester. Many module manufacturers are offering lifetime warranties, even with the lower grade chips. The reasons can be varied, but essentially they are placing a pretty good bet, because most people don't keep their systems for more than a few years - and many don't push their systems enough to really give the memory a workout. Low quality chips (and even used ones) may work well for years, but their reliability is less than new, A-grade chips. It may take you many months (or longer) to figure out that those GPFs and application errors are due to bad memory chips, rather than the software. The dealers who are offering one, three or five year warranties are likely getting their product via the gray market, or are manufacturing modules using C-grade or used chips. These days it is best to avoid these dealers, if possible, unless your major consideration is price rather than reliability. Unfortunately, just looking at the name on the chip is not enough, these days. With the market being so competitive, there are many questionable practices being used to cut costs for the reseller - usually at the expense of the consumer. Knowing what to look for can reduce the chances of your getting one of these low-grade SIMMs. This means understanding the markings on the chips and recognizing the signs. REMANUFACTURED MODULES - One of the most prevalent practices used, and one to watch out for is remanufactured memory modules. As stated above, each manufacturer puts a date stamp on the chip (except for TI, who puts it on the PCB). This date stamp will be in the form of 'YYWW', where 'YY' is the year and 'WW' is the week of manufacture. For example, a stamp of 9622 means the chip was manufactured in the 22nd week of 1996. This is how all reputable memory module manufacturers make their SIMMs. You may see that the data chips and the parity chips have different dates from each other (i.e., they are different configurations), but they should not be too far apart. If these dates do not match, then there is an extremely good chance that the memory has been remanufactured. In addition, these chips are typically used chips, which you may not be willing to buy regardless of price. Another issue in regards to remanufactured modules is that the manufacturer may be using 4Mb chips to build a 16MB module. This can cause excessive heat to build up in the module during use, and can damage the chips, thereby affecting it's reliability, or rendering it unusable. See the section on Memory Modules for an explanation of composite vs. REMARKED CHIPS - Another practice to be aware of is the remarking of chips. This is extremely questionable, if not outright fraudulent. There is virtually no way to know anything about that chip once it's markings have been removed, without testing it. You can identify remarked chips most of the time as follows. Since a remarked chip must be sanded or etched, the surface of the chip will be very dull, or matte, in appearance. Also, the recessed 'dots' will not have sharp edges, and will no longer be reflective when held under a light. Finally, if you can scratch the manufacturer's markings off with a fingernail or knife, the chip has most certainly been remarked. As stated previously, some chips are remarked at the factory. Just because the chip does not carry the name of a major IC manufacturer, does not mean the chip has been fraudulently remarked. By observing the above signs, you should be able to determine if the chip was remarked at the factory, or by some unscrupulous manufacturer or vendor trying to cut costs. USED MEMORY - A practice that is used mostly at computer shows and swap meets is to sell used memory. Many times, people have traded in their old memory so they can get a price reduction on 'new' SIMMs. These dealers then turn around and sell the memory back to the next guy, and makes a pretty good profit, to boot! You can avoid this by checking the dates on the chips, as specified previously. Any SIMM with a date more than a year old has likely been used before - and anyone who says that it may have sat on a shelf for that long is either rationalizing, lying or is simply being argumentative. Memory is just too volatile for any dealer who wants to stay in business to have sitting around for any length of time. Another way of identifying used memory is to look at the leads and see if it is 'dimpled' or scratched up, though if the dates are pretty recent, it may have just been put into a machine to be tested. If you have to return a module to a vendor that is many miles away, the cost and time of returning it will offset any savings you may have thought you had received. FAKE PARITY - If you have a need (or desire) for parity memory, be aware that there is quite a bit of 'logic' or 'bit' parity being sold. This is where the parity bit is not stored at write time, but is instead generated at read time. This guarantees that an 'OK' signal is always sent to the memory controller, so that, in effect, there is no parity checking at all. This may have made sense for 30-pin memory when memory was still expensive (and parity was about 12% more cost, since it required 9 chips instead of 8). If you didn't want parity, you couldn't use non-parity memory in the 30-pin slots, so the fake parity was a reasonable solution. Unfortunately, the practice has spilled over into the 72-pin market. A lot of what is sold as parity memory is, in reality, 'logic' parity. My question is, if you can buy and use non-parity modules, what is the purpose of fake parity? My guess is to defraud people out of their hard-earned money. Fortunately, there is a fairly easy way to identify fake parity. On the parity chip, all manufacturers that I have checked out place a 'BP' stamp along with their regular marks. This stands for 'bit parity'. As long as the chip has not been remarked, you can identify it. A couple of issues to take note of, that are not related to fraudulent practices have to do with compatibility, power and performance. The more you know about your motherboard (and system in general) the better your protection against fraud and incompatibilities. MEMORY SPEED - Every motherboard will have a document that specifies what speed memory to use. This is usually specified similar to 'Use 70ns or faster'. This means that a 60ns module will work, but an 80ns will likely cause system crashes and hangs. While it is possible to mix speeds on a motherboard, all modules in a given bank should have the same speed rating. Note that a speed rating is the *minimum* speed of the chip - it may actually perform faster. EDO vs. FPM - Many Pentium motherboards (and a few 486 boards) will accept EDO modules. While these were originally advertised as being significantly faster than FPM modules (see Memory Chip section), in actual practice the greatest impact on performance is the quantity and type of L2 cache is on the motherboard. Another thing to consider is, if you want parity checking you are stuck with FPM, since EDO is not currently offered with parity. SDRAM - Currently, SDRAM modules are about 50% more expensive than SIMM modules, and they only provide a marginal performance improvement (for the same reason that EDO doesn't provide much improvement). This has lead a number of people to claim that SDRAM memory is not worth buying. There is, however, one issue that may have been overlooked. With asynchronous DRAM, you need to replace the modules each time your bus speed increases, but with SDRAM, the memory will continue to increase in speed as you increase your bus speed (up to 125MHz, as of this writing). This means that in the long run, you may actually save money by purchasing SDRAM if you plan to replace your motherboard with newer models having faster bus speeds. TIN vs. GOLD LEADS - There have been some reports of corrosion on the leads and connectors when the metals do not match. Generally speaking, you should be sure to buy modules with leads that match your connectors. COMPAQ computers are notorious for having gold connectors, so you should buy modules with gold leads. If you have tin connectors, buy tin leads. Apparently there is some type of reaction that occurs in humid environments that corrodes the metals, and can break the contact. This can cause problems from intermittent memory related errors to the machine unable to boot. DOUBLE-BANK MODULES - Some motherboards will use two banks with the 8 and 32 MB modules so that one slot remains empty, while other motherboards may not recognize these modules at all. As a result, it is very important to check your motherboard manual before buying 8 or 32 MB modules. One very common situation arises on 486 boards with both 30-pin and 72-pin slots. Usually, with these boards if you fill up the 30-pin slots you cannot use double bank modules in the 72-pin slots. Again, check your motherboard manual to prevent buying a module that you cannot use, or that requires you remove the memory in your 30-pin slots (unless that is what you intended to do in the first place). 2 and 4 CHIP MODULES - Although these types of modules are generally no problem, some motherboards will not accept 4 chip memory (8 MB modules made with 16Mb chips), due to the use of a non-standard module design. As described in the section on memory modules, it is possible to make a single-bank 8 MB module with the 16Mb chips, but they must 'emulate' a double bank module via a special TTL chip. There are motherboards that do not like this configuration, causing the modules to be unrecognized, or rendering the system incapable of booting. I have not heard of any incompatibilites with 2-chip memory modules. REFRESH RATE - You should try to find memory modules that have a 2K, rather than 4K refresh rate (see the section on Memory Chips for an explanation of refresh). These modules will require less power, and will be better and more reliable performers in the long haul, due to less heat buildup. They will also be able to more reliably maintain the data due to the faster refresh rate. MIXING MEMORY - There have been numerous postings and questions on mixing memory on a motherboard. The general rule is that as long as you have the same type of memory within each bank, and the motherboard supports the type of memory you are installing, you probably won't run into any problems. There may be exceptions to this, and as always, the final determination is based upon what your motherboard documentation states. If your motherboard support EDO, for example, it will very likely not complain if you install the EDO in one bank and FPM in another bank. Some motherboards, however will treat all of the modules as FPM in this case, while others will be able to utilize the modules properly. Some motherboards require the EDO to be installed in the first bank in order for the module to be recognized as EDO. Also, if your motherboard does not explicitly state that it supports EDO, the board may or may not be able to utilize these modules at all (meaning, it may not even boot!). Another area of controversy is mixing modules with different speeds on the motherboard. Again, generally speaking as long as you install the same speed modules within the same bank, you should not have a problem. One issue to recognize is that if the memory is slower than the motherboard requires (this is based upon the bus speed), you may need to increase your wait states (BIOS setting) to ensure the motherboard will run properly. This is because the bus 'expects' the data to be available on the next transfer cycle. If the memory is too slow, the board may experience lock ups, addressability errors, etc., so making the bus 'wait' another cycle will ensure the data is waiting when the bus makes the transfer. Many people have posted information saying that the system will run at the speed of the 'slowest' module, so mixing of speeds should be avoided. While I have no hard data to prove or disprove this, it seems unlikely that this really occurs, except as outlined here. As described in the section on memory chips, a chip that is rated at 60ns may actually perform much faster. All that matters, as far as the bus is concerned, is that the memory be available for the next data transfer. This means that all modules - regardless of their speed - will perform the same in terms of the overall performance, because it is the bus that determines how fast the data is delivered to the cache or CPU. If you bump up the wait states to accomodate slower memory, then all of the memory accesses will slow down because the bus waits the extra cycle(s). This does not mean, however, that the memory chips actually 'slow down' internally. It is important to recognize that, as with most things in life, you get what you pay for. If someone is offering a product that is significantly less than everyone else is selling it for, there is a good chance that it is of poor quality. Buying from local computer shows can be very risky, even for those that know what to look for. Although the memory may be cheaper than you can get it for elsewhere, you may find that the SIMM fails in a few days, months or even years later - and a lifetime warranty is useless if you can't track down the seller. Even if you can return it, your savings may be offset by the cost of shipping or driving it back - not even considering the loss of use. While this has not been substantiated by myself, nor have I seen any official benchmarks of this nature, it is certainly food for thought. Of course, it may be possible to find new, quality memory for a 'killer' price at a show, and you may also find that some mail-order companies or your local retailer is selling low-grade memory so they can offer a lower price. The key is to get references from others who have bought memory, and learning as much as possible about memory in general before handing over your money. Dealers who do not understand memory technology and manufacturing practices may actually have unknowingly purchased sub-standard modules themselves, thereby 'honestly' ripping you off. Always demanding the lowest price may be asking for trouble. It is up to you, the consumer, to protect yourself, and it is ultimately you who pay the price for poor quality. You should always ask where the memory came from. The best way to prevent getting substandard product is to know where the product came from, and how it has been tested (if at all). This is as true for the retailer as for the consumer. If the reseller does not know who the manufacturer is, there is a chance that they are buying poor quality memory. In addition, many dealers claim to have 'tested' their memory, but the question is what type of memory tester did they use. The standard 'SIMM Checker' will test speed and configuration, but is not very good at testing reliability. Another tester know as the 'Dark Horse' will test *only* for reliability, but does it extremely well. Another item to consider is who will honor the warranty. If, for any reason, your module becomes damaged or unusable during the warranty period, you will want to have that module replaced. As long as the vendor is still in business, and is willing, you will likely get your warranty honored. However, if that dealer has moved, filed for bankrupcy, gone out of business, or is just unwilling to honor the warranty, you will be stuck. By knowing who the manufacturer is, and having a manufacturer's warranty, you can be sure that somebody will replace your module. In fact, the vendor is more likely to honor the warranty because he knows the manufacturer will replace it. One of the things we are all guilty of at some time, is assuming that the guy with the lowest price is being honest, while the guys who are a bit higher are all trying to rip us off. Pay attention to what retail and mail-order prices are (excluding the 'superstores', which are notoriously expensive). If you are offered a similar product for 20% or 30% less than what the 'going rate' is, be very suspicious - there's usually not that much of a markup in this industry anymore. I would like to thank Doug Krone of Advantage Memory Corp, Mike Harris of TechWorks, Inc., and Larry Condo of Century Microelectronics Corp. I also would like to thank Jay Kee and Micheal Zamarocy (Forum managers on the Microsoft Network) for encouraging me to write this article in an objective and informative manner, and for volunteering to distribute it. Additional thanks go to Bryan Gridley, Dan Abrams and William Farley who responded to my original article that was posted in several Usenet forums, and provided me with some corrections and additional sources of information. Copyright 1996, Real World Industries To get to the Live From Mars Frequently Asked Questions site click HERE. This site has dozens of answers to FAQ's about Mars and Mars missions. Also, to hear the daily report on the Mars Pathfinder Mission from North America call 1-800-391-6654 and press 3. Other mission reports are also available at that number. For those of you who like trivia and factoids, here is a table listing some of the Sojourner microrover's accomplishments. Yes, there is some relative uncertainty in a few of these values. In particular, the odometry values have some statistical error due to gyro drift, accelerometer noise and rover driver heading and position corrections. On September 26, 1997 we had our last communications session with the rover; that was Sol 83. During normal operations, each command sequence that is uplinked to the rover contains what we call a runout sequence. It is basically six days of stationary rover activity consisting mainly of automatic health checks, and MAE or APXS experiments. Sol 81 was the last day that a new sequence was uplinked to the rover, so on Sol 83 the rover was already two days into its runout sequence. There are no driving commands performed during the runout sequence so the rover just sits and waits. On Sol 87, the sixth day of the runout sequence, the rover automatically went into contingency mode operation. Contingency mode is implemented under the assumption that the rover has gone out of communications range with the lander by either driving too far away, into a trench or behind a big rock. To help regain communications, when activated, the contingency sequence commands the rover to drive to the origin or center of the lander (Sagan Memorial Station), in effect, go find papa! If during its trek back to the lander, communications is restored, then a new set of commands will be received by the rover ending its contingency operations. If communications is not restored then the rover will try to get there, autonomously on its own. The center of the lander is by design inaccessible to the rover, it can't physically get there. For that reason a 3 meter radius virtual barrier or wall was programmed in the rover software to prevent it from accidently driving into the lander. This 3 meter stayout zone can be reprogrammed by ground controllers if necessary. When the lander problems began, the rover was at a rock named Chimp, where it had performed an APXS measurement on Sol 81 and 82. Chimp is about 9.3 meters radially from the center of the lander. The drive back to the lander could have taken several different paths. One possibility is that the rover drove in an arcing turn toward the lander as shown HERE. Note, the forward ramp is inside the 3 meter stayout zone. In another possibility, the rover would have executed a left turn and made a beeline to the lander. This path would have possibly taken it between the rocks named Hassock and Wedge. However, the rover uses its hazard avoidance to drive around rocks, so if it encountered any nearer rocks, particularly Ender, first it would have tried to drive to either side of them depending on its assessment of the hazards. If it got past those obstacles (located about 4.5 meters from the lander) without any severe driving (articulation) errors it could have arrived at the lander in the vicinity of either the forward ramp or near Torres rock. If it made it to the 3 meter virtual wall, it would have stopped and then tried to drive around the wall like any other hazard. But since it's onboard software won't let it go beyond the 3 meters, the rover will begin to drive around the lander following an arcing circular path. Since the rover has inherent drift in its gyros and somewhat noisy accelerometers, its autonomous driving may incur heading errors. Depending on how long the rover is driving, the accumulated heading errors may cause it to spiral toward or away from the lander, possibly getting it into trouble. In it's driving around the Lander, Sojourner could have accidently driven up onto a rock and received a traverse error that would have stopped it. If that has happened then the rover will be parked in that one spot, never to move again on her own. Without any hard data, these scenarios are mere speculation based on how we know the rover operates. In reality we just don't know and may never know where Sojourner is and what she's doing right now. What happened to the Rover Telecom System on Sol 1 and 2? Before we discuss the assessment of the rover telecommunications problems encountered on Sol 1 and Sol 2, we want to preface this FAQ by pointing out the simple nature of the telecommunications hardware in use. These are commercial 'off-the-shelf' radios that have been upgraded to the best of our abilities with available time and funding to meet the flight requirements. These radios have only temperature monitoring telemetry of internal hardware conditions, so it is difficult to truly understand how well they are functioning in this new environment. For that reason any conclusion of what happened on those two days, based on such meager measured data, is still somewhat speculative. Nonetheless, a best estimate of what we feel happened will be presented. Note however, that the Motorola radio modems *DID NOT* suffer a hardware failure at any time, and given the current circumstances are working as expected; nothing had to be fixed, just understood. It is unfortunate that the July 5 press briefing was not as revealing as it could have been. Unfortunately, the (sometimes) interrogative nature of press briefings tend to pressure the person being interviewed to confess an answer, even though there isn't one. Not to fault the media, but sometimes the 'possible answers' get transformed into 'the answer' and published that way. That is just the nature of the beast! Hopefully, this FAQ will set the record straight and help those interested understand what happened, how it was overcome and how we are proceeding now. Sol 1 Rover Telecom Scenario. The first level 2 health check data was unusable and a subsequent commanded level 3 health check (cmd 1034) was performed (291 bytes) at 07:35 TLST. Telemetry from the rover health check indicated that the telecom system was working nominally with a modem operating temperature of -4.0 degree C. At approximately 7:40 TLST the lander LGA downlink session had ended. A large number of garbled frames (ones containing CRC errors), however, continued to be received by the LMRE modem which indicated that the rover modem was still transmitting, and had not completely failed. During this period when rover communications degraded, the lander switched from the low gain antenna to the high gain antenna after doing a sun-search with the IMP camera. Also, the lander +Y petal was elevated to 45°, an air-bag retraction sequence was run, and the petal was returned to a horizontal position. All during this time the rover was in the stowed configuration on the petal with its antenna down. Lastly, it was decided not to deploy both rover ramps that day and wait until Sol 2 to perform the rover release, standup and egress down the rear ramp. Sol 1 Considerations. The following have been considered and discounted as communications problems. A lander AIM software problem interfacing with the LMRE modem. A lander Bus voltage problem with the LMRE DC-DC converter board. An interference condition (modem front-end overload) between the lander microwave transmissions and the rover UHF communications. Rover or lander UART crystal frequency drift. Rover CPU crystal oscillator failure. LMRE voltage regulator failure. LMRE low power latchup condition. A failure of the HOLD_COM command sequence in the rover sequence. Increased Solar X-ray and Proton Flux activity which would induce an SEU event. An SEU event in either the LMRE modem or Rover modem. No events were detected or recorded. A disconnection of the SMA RF connector to the LMRE antenna. A 24C temperature difference between the LMRE radio and the Rover radio causing shifts in the Rover TX and LMRE RX frequencies. The LO crystals' nominal center frequency are temperature dependent. The Rover antenna was in the stowed configuration creating a cross-polarized condition between the two UHF monopole antennas. Based on measured data with a rover model, this results in at least 10 dB of attenuation in the UHF RF link. Also while stowed the rover antenna is in close proximity to the solar panel which significantly de-tunes the antenna. Furthermore, the LMRE antenna in this configuration is blocked to the line-of-sight of the rover by the lander LGA. The rover location on the +Y petal is in a region of significant RF scattering and multi-path caused by the lander structure. The environment close to the lander was somewhat noisy electrically during this time, with stepper motors, actuators and electronics causing a general increase in the noise floor in the vicinity. Analysis of the garbled frames received by the LMRE radio indicated a large number of CRC (Cyclic Redundancy Character) errors and short frame errors (truncated frames) which implies that the telecom link BER performance was degraded. There is no forward error correction channel coding whatsoever. The response of ACK or NAK is dictated by a cyclic redundancy character (CRC) check computed over the contents of each transmission frame. A frame generating a NAK can be retried up to three times before the software errors-out and skips to the next frame in the transmission queue. Click on this graph to see how quickly the probability of receiving different size data frames can change with just a small change in BER performance. Note also that short frames (those of length less than 256 bytes) can get through with high probability even when those of maximal length are likely to fail. It was this phenomenon that was frequently observed on Sol 1 and 2: short frames got across; fully stuffed frames did not. Clearly the communications problem was not one of hardware failure or even of hardware intermittency, it was mainly an increase in bit error rate that was coupled with an extremely unforgiving data-link layer protocol. During the night of Sol 1 the lander experienced a software reset which, it was concluded, did not affect the quality of the lander to rover UHF link. Also, it has been concluded that switching between both LMRE modem DC-DC converters had no corrective effect. The changes may have made the communications environment favorable enough to allow reception to occur. The small packet size command sequences for Sol 2 were received normally by the rover. This indicated that the rover was able to receive data without any problems and it then began transmitting data to the lander. Its first level 2 health check showed that its modem temperature was at -30C, much colder than the day before because no modem heating was performed. The rover was still stowed on the petal with its antenna down. Data was apparently buffered on the rover overnight when comm with the lander was lost. A total of 31,491 bytes of sequence data were received. This coincides with the amount of EEPROM available for buffering data (32K). All indications are that the rover continued to perform runout or commanded sequence but had no place to buffer the additional data. The rover UNSTOW (cmd 2520) command executed nominally at 11:47:55 TLST. At that time the rover UHF antenna was deployed and a co-polarization condition existed between the rover and lander antennas. LMRE Link quality was marginal, and remained at about 40% until rover egress. During the second downlink pass no rover data was received because a mistake in the lander sequence turned off the LMRE radio from 8:01 and 11:29 TLST. This was reflected in the rover telemetry as an increase in timeout errors as the rover attempted to communicate with the lander which was not receiving. In downlink session 3 the rover was executing the remainder of its final Sol 2 sequence which included the petal egress move down the rear ramp. The traverse down the rear ramp is executed with two 1000mm MOVE commands. After confirmation of the first MOVE command, which would have placed the rover half way down the ramp at 14:40 TLST, communication with the rover ceased. Rover telemetry at that moment showed the modem temperature to be +30C. Lander data also indicates that at that time the LMRE link quality had degraded to 27%. With 100% link quality, it should have taken just 12 seconds to receive the second move command. At a lower link quality it would have taken significantly longer to receive. Being unable to send the MOVE command data, the rover buffered that data to memory. From examination of the rover sequence (50250V), after the rover performed the second MOVE command, it was commanded to HOLD_COMM and deploy the APXS onto the soil. After that was done, HOLD_COMM was removed. Note, it is a requirement that rover communication be suspended during APXS activities because a +9V DC converter is switched from the modem to the APXS (both can't operate at the same time). With 6 wheels on soil and in a slightly different location after the final MOVE, the rover then successfully transmitted some of its buffered telemetry data to the lander, including a rover image taken of the front ramp. The rover performed an auto shutdown at 19:08 TLST and remained silent (no communication) for approximately 18 hours and took two APXS spectra of the soil. A thermal problem with either the lander or rover radio. A relative angular condition between antennas that would have caused cross polarization problems. Rover or lander command sequence execution. There were no commands that would have suspended communications during the final move down the ramp. An SEU latchup condition in either the LMRE or Rover radio modem. No events were detected or recorded. The following are the most likely contributors to the communication degradation problem: Scattering and multipath conditions caused by close proximity to the lander +Y petal and rear rover metallic ramp. Degraded BER performance which delayed lander receipt of the second MOVE command that was fairly large, 3628 bytes, in size. The rover woke up nominally on Sol 3 using solar power at 05:27 TLST. The command sequence for Sol 3 was queued on time and sent to the rover. The rover radio modem was much warmer (-15C) at wake up on Sol 3 than Sol 2 because of the execution of a modem heating command. The two overnight Sol 2 APXS spectra were received by the rover, as well as the remainder of the final Sol 2 telemetry. The Sol 3 sequence performed very well , with the rover positioning its APXS on the rock named 'Barnacle Bill' for the night. The only problem relating to telecom was 15 lost packets of rear image data with all other data successfully sent. Conclusions and Operational Recommendations for the Mission: Initially, all rover telecom problems were caused by a combination of environmental and configuration conditions. At no time were there any electrical, mechanical or software interface failures with either the LMRE radio modem or Rover radio modem. LMRE modem UART interface resets occur periodically, and are a normal part of operation on that side of the interface. Rover modem resets occur when the rover performs a SHUTDOWN or any APXS related activities in its sequence, which requires a HOLD_COMM command. Note, the only factor which has not been conclusively ruled out is the possibility of the loss of hermeticity in one of the four crystal local oscillator cans used in the modems. The crystals utilize a solder perform hermetic seal between the base and body of the can, in a 1 atmosphere inert gas. A failure of the hermetic seal would definitely shift the crystal's frequency (on Mars) because of the change in pressure inside the can. According to the manufacturer, a 5 PPM change in the resonant frequency of the crystal equates to about a 2 KHz frequency shift of the output frequency. But, based on the recent operation of the telecom system, and a comparison of pre-delivery thermal test data performed at JPL and thermal data from Mars, the probability of loss of hermeticity is low. If it has occurred, the consensus is that the LMRE receive LO crystal may be the culprit. This experiment would be time consuming to perform and give insight into only one problem associated with the function of the radio modems. It should be noted that the aging process of the LO crystal has been examined carefully. Pre-launch lab measurements of three spare radio units showed that the aging rate is consistent with expectation for that design, namely a negative drift in the output frequency that is logarithmic in time; linear on a log-scale of time. For present and future Sojourner rover telecommunications activities, here are the recommendations for hardware usage and when planning operations sequences. Perform 10 to 15 minutes of rover modem heating and 5 minutes of reheating (if needed) after each rover morning wakeup. The goal is to have the rover modem above -10C for early morning communications. Keep the rover in line-of-sight of the lander at all times. This includes not driving the rover down into trenches or behind large rocks like Yogi Bear. Be aware of where the lander and rover null zones are located when planning traverses. Know the location of the HGA and its configuration relative to the LMRE antenna, in particular, when the flops in the clock (elevation) angle are to occur. When it is desired to transmit large volumes of data, like images, endeavor to begin this activity when the rover modem is above +25C. Based on accumulated Sols experience, the telecommunications system link quality is best when the rover modem temperature is between +30C and +40C, and when the LMRE modem is at +20C or above. This may involve doing a short duration modem heat just before the taking and transmission of multiple rover images. This will improve telecom throughput at the expense of losing some data. Have a lander software mechanism ready to implement which can buffer garbled frames in lander memory for later downlink and reconstruction on the ground, allowing the recovery of lost image packets. Have a series of rover sequence commands which will buffer rover images into RAM for later transmission if needed. Most of the these recommendations have already been communicated to the Mars Pathfinder project and implemented as part of rover operations. They have proved to work leading to significant improvements in the functioning of the Sojourner telecommunications subsystem. How do the radio modem telecom protocols work? The rover and lander UHF radios communicate using a relatively simple protocol. The rover is the master of the link and controls the initiation of all communication sessions. The session types are Heartbeat, Time Request, Command Request, and Telemetry. The function of the lander radio is to upload fairly small command sequences to the rover and receive data and telemetry downloads from it. Data transfer is performed in sessions whereby individual frames are exchanged in a well controlled manner. A frame is a group of byte-aligned data that is transmitted during keying of the RF modem. The maximum length of a data frame is 256 bytes and many, like a 250 byte telemetry frame, are shorter. A complete frame consists of: A 6 byte acknowledge (ACK) frame, which contains a Sync code, a frame ID (FID), a frame number (FNUM) and a CRC value. Followed by the 256 bytes of data, for a total of 262 bytes. Commands, data (like images and APXS spectra) and telemetry are organized into packets. Some packets are small and some are large depending on what they contain. By definition, the largest number of bytes that a packet can have is 2000. Also by definition, a session contains 8 frames. The packets are further divided up into frames for transmission. For example, a 600 byte packet is composed of a minimum of 3 frames (two 256 byte frames and an 88 byte frame). All communication sessions begin with a Start frame that is sent by the rover, followed by an ACK (acknowledge) from the lander. Heartbeats are very small and done most frequently to assure that the rover has not driven out of communication range. Time Requests are used to resynchronize the rover clock with the lander clock. Command Requests are done when the rover wants an upload of a new command sequence stored on the lander. Telemetry is rover engineering or science information that needs to be sent back to Earth. If for some reason there is a problem with radio transmission and the lander receives a bad ACK frame, then it will send the rover an NAK (No Acknowledge) response. The rover will try to resend frames up to 3 times. If it can't get the frame across then it goes on to the next frame. Dropped frames do indicate incomplete packets. Images are particularly sensitive to lost frames, because they cause missing strips or sections in the image. The standard interface on these radios is RS232, however, the RS232 converter chip on the digital board has been replaced with jumpers so that 0-5 v TTL levels could be used with the rover and lander computers. The radios are hardwired for 9600 bps asynchronous communication with 8 data bits and 1 stop bit. How does the lander telecom system communicate with Earth? The microwave data link from the Mars Pathfinder lander to earth is handled by the Deep Space Network, in many ways a more remarkable link than the lander to rover link described in our web page. Information on the DSN can be found HERE. Basically the DSN consists of a collection of 34 and 70-meter radio telescopes equipped with cryogenically cooled low-noise amplifiers (approx. 20 degrees Kelvin noise temp). They operate in two microwave frequency bands, S-band (2.3 GHz) and X-band (7.2 GHz TX uplink , 8.4 GHz RX downlink). For detailed information on the technical specifications of the DSN antennas click HERE. Mars Pathfinder uses a 12 Watt RF output X-band SSPA (Solid State Power Amplifier) transmitter to downlink data back to Earth. The X-SSPA was designed, built and tested here at JPL. The Pathfinder lander has two different ways to communicate with Earth. One way is with its High Gain Antenna (HGA) and another is with its Low Gain Antenna (LGA). Both antennas are capable of receiving the 7.2 GHz uplink signal and transmitting the 8.4 GHz downlink signal. When the LGA antenna is used, its maximum data rate is 600 bps. The LGA is a choked circular waveguide design having about an 70 degree 3 dB beamwidth pattern with about 6 dBic of peak boresite gain at 7.2 and 8.4 GHz. When the HGA is in use, the maximum data rate, based upon the performance of the Pathfinder RFS (Radio Frequency Subsystem), also built at JPL, is 11.06 Kbps. At Mars distances (depending on spacecraft configuration), the signals can easily be tracked by phase-lock loops with a 1 Hz loop bandwidth. Uplinks to the Pathfinder lander are sent via high power transmitters (up to 20 KW) through the same high gain 34 or 70m antennas. Typically though we run Pathfinder high gain downlinks at 8.250 Kbps. The HGA on the lander was provided by Ball Aerospace and is a printed dipole array design utilizing a meanderline RCP (Right Circularly Polarized) polarizer. It is about 11 in diameter and weighs 1.2 Kg. It has about 20.4 dBic boresite gain at 7.2 GHz and 25 dBic boresite gain at 8.4 GHz. In all, the telecommunications systems used for deep space are large, complex and expensive to build, maintain and operate. The radio signal transmitted from Mars to Earth is so weak by the time it gets here it is only 7.9x10-19 watts. That is much too weak for the average home satellite dish and receiver to detect and discriminate from random background noise. So all of you folks out there with satellite dishes can relax, because this channel is one you'll have a tough time receiving. For now, just sit back and enjoy the wealth of new images and data from Mars, brought to you by NASA-JPL and paid for by your tax dollar contributions. How far is the communications range of the rover? It performed quite well under environmental conditions typical of a warm (35C) August day. Another less controlled test (done about a year earlier) of the telecom system was made at a farther distance and under more extreme conditions. A stationary radio modem, antenna and computer interface was set up outside on the Mesa antenna range. Another modem and laptop computer were placed on the bed of a truck, with someone monitoring its operation. The truck was driven to different locations on the Mesa as well as down the winding paved road to the main part of the lab. The distance down to the lab was well over 700 meters and given the surrounding buildings and other structures, had quite an abundance of multipath reflections. The radio modems performed well under those conditions and did not lose communications. Both of these tests were performed under warmer conditions which is where these radios like to be operated. Many other tests of these radios were conducted under controlled thermal and signal attenuation conditions. We found that under certain conditions, there can be a degradation in the quality of the communications link. In particular at the lowest acceptable operating temperature of -30C, the Bit Error Rate (BER), due to an operating frequency shift may under certain conditions cause a communications blackout problem. Given the terrain we landed on it is difficult to predict just how far we can go with the rover. However, if the rover is kept in the line of sight of the lander and the radios are kept at a warmer operating temperature, the range of the rover telecommunications system should be at least 700 meters. The real constraint on how far we can drive is based upon the stereo imaging range of the lander IMP camera. Beyond about 10 meters, the IMP camera resolution may not be able to provide good enough stereo coverage of a particular location to assist the rover navigation team in driving the rover. This would put additional weight on the rover to get its navigation information from its own stereo cameras. This is not impossible, but certainly more tricky to plan and implement. If this is undertaken, the rover traverses would most likely be on the order of 2 meters at a time, because that is about the distance a ray can be projected by the rover cameras. However, given the banquet of interesting geologic formations near the lander, the scientists are content, for now, to remain in the immediate vicinity of the lander. If we do venture out farther, we will most likely travel to a location that is higher in elevation than the lander and in line-of-sight. The hill beyond Desert Princess is a prime area for this type of venture. What is the communications delay between Earth and Mars? We communicate with the Pathfinder lander using radio waves, which travel at a speed of 2.9979245x108meters per second. During the July 4 landing Mars and Earth were 192 million Km apart. At that distance it took 10 minutes and 39 seconds for the radio signal to travel in one direction. Because of their orbits, Mars and Earth are moving farther apart. As of November 6 Mars is approximately 291 million Km from Earth. At that distance it now takes 16 minutes and 10 seconds for the signals to travel in one direction. On May 13, 1998 Earth and Mars will be in conjunction (opposite sides of the sun) at a distance of 2.49 AU (1 AU is defined as 1.4956x1011 meters). At that distance it will take 20 minutes and 42 seconds for a radio signal to reach Mars. On June 22, 1998 Earth and Mars will be their farthest apart at 2.52 AU. At that distance it will take 20 minutes and 57 seconds for a radio signal from Earth to reach mars. It is these time delays which makes it impossible to communicate with and control the rover in real time. Why are the rover batteries not rechargeable? The primary source of power for the rover is its 0.22-square meter (1.9-sq-ft) solar panel, and not its batteries. The solar panel is comprised of 13 strings of 18 GaAs (Gallium Arsenide) cells. The power output of the panel at noon on Mars is approximately 16 W (@ 17V). Power from the solar panel alone is sufficient to operate the rover for several hours during the day. In fact the rover was designed primarily to run on solar alone with the batteries providing backup power when needed. The LiSOCL2batteries are arranged in 3 strings of diode-isolated D-cells. They are placed in 3 tubes located at the rear inside the Warm Electronics Box (WEB) next to the RHU's (Radioisotope Heating Units). These batteries can provide 150 W-hours of energy and supply 8-11 volts of unregulated power to the different converters and regulators attached to the core power bus. There is also a tiny Lithium battery located inside the APXS sensor; it is also not rechargeable. When the power subsystem for the rover was being designed, mass and size requirements for the battery pack were already defined, based upon available space inside the WEB. It *WAS NOT* a requirement that the batteries be rechargeable. An industry search was made for 'class-D flight' batteries which would meet these requirements, as well as energy output. Lithium Thionyl Chloride battery technology at the time had some flight heritage (space shuttle) and could provide a solution for these needs, so the choice was made to use these cells which met the requirements. Further, rechargeable batteries, like NiCads require a trickle charge to keep them alive. During cruise the rover solar cells are in the dark and useless for charging, also the rover had no power supply conection to the lander. At this time the primary mission objectives have been met and we are now into the 'extended' rover mission, which is baselined at 30 Sols. After that all APXS activities will be shifted to daytime operations. As of August 30, the rover batteries became totally depleted, so from that point forward we are on a 'solar-only' mission. How long can the rover keep functioning on Mars? This question is difficult to answer for there are many unknowns to consider. The rover hardware was designed to last at least seven days which it has done. Even when the rover batteries become depleted, we can still function during the daytime. After that, if the solar panel fails or becomes covered with dust, the rover will not have enough power to operate. A sudden dust storm could cut short the rovers life by obscuring the sun and covering its solar panel with dust. Also, during the winter months on Mars, it will be much colder and the sun will be lower in the Martian sky. During that season the lander may stop functioning because it can't generate enough solar power to keep running some of its mission critical hardware, like the telecommunications system. If the batteries on the lander do become depleted and it is decided to not keep recharging them, then we can run a lander 'solar-only' mission. If that were to happen then operation on Mars would be limited to between 4 and 6 hours a day. Furthermore, the orbital positions of Earth and Mars are changing and moving the planets farther apart. On May 13, 1998 Earth and Mars will be in conjunction (opposite sides of the sun) at a distance of 2.49 AU (1 AU is defined as 1.4956x1011 meters). This distance is not as much of a problem as having the sun in the way, for it produces a lot of radio interference making communication almost impossible. Indeed, for distances of less than 10 solar radii around the sun, the thermal noise contribution is quite severe. From and engineering perspective, the biggest enemy of the lander and rover are the extreme temperature swings that occur on a daily basis. These temperature cycles produce mechanical fatigue on the exposed components which can lead to a failure. For now it is safe to say that the rover will function as long as the lander does, for once it falls silent there will be no more communicating with the rover. Footnote: As of November 4, 1997 the Mars Pathfinder mission has entered the Contingency Mission Phase. No data has been received from the lander or rover since September 26 and no communications at all since October 7. In all probability the lander is too cold to operate its systems and has fallen silent. Can I get schematics and engineering drawings of the Microrover? Unfortunately, the electrical schematics, mechanical drawings and blueprints of the rover are not available for public distribution; they are intended for JPL internal use only. Information about certain specifications or operating conditions for the hardware can be released to the general public after such information has been cleared for release by JPL document control. The intent of not releasing all this information into the public domain is to protect the vested interests of NASA, JPL and their contractors. However, some technical information has been made available to the public, and inquiries about JPL technical documents may be sent to Elizabeth Moorthy of the JPL Archives and Records Group at the address archives@jpl.nasa.gov. You may also write or call: Archives and Records Group Where can I download Rover and Lander images? There are several web sites that have been established for the dissemination of Mars Pathfinder images into the public domain. Here are ones that are known, and as new sites are found, they will be posted here as well. Mars Pathfinder Mission Images - Click on the Mars Sol day to see selected images from that day. Arizona State IMP Camera Images - Click on the Image Archive link to see Pathfinder Images. NASA Planetary Photo Journal - Click on the planet Mars then select either the Mars Pathfinder Lander or Rover image database. Rover Telecom Pictures site - Good grab bag of images of the Rover hardware, video clips and selected images from the IMP and Rover camera. Mars Pathfinder News and Information site - Another source of selected Lander and Rover Images from the mission. Rover Animated GIFs - Great place to see animated GIFs of the Rover taken by the IMP camera. Mars Pathfinder Image site - A clickable directory listing of Lander and Rover Images. Does the Lander or the Rover have a microphone for recording sounds? The answer is no. For this mission, it was not one of the scientific objectives to study atmospheric acoustics on Mars. Perhaps future missions that are sent to study the Martian atmosphere will have instruments for measuring sound. If such instruments were used, what are the things we could listen for? The wheel motor sounds from the rover as it drives on the Martian soil. Martian winds rushing by the rocks (eolian sounds). Possible lightning strikes or discharges in the thin atmosphere. Explosions from meteor impacts. Eruption sounds from volcanic activity. Atmospheric displacements due to Marsquakes. Background acoustic noises caused by infrasonics. The atmospheric pressure on Mars is between 6 and 10 Torr (units of Torr are mm Hg @ 0C and 1 Torr = 1.33X10-3 bar), compared to 735.56 Torr (at sea level) for atmospheric pressure on Earth. Here are two equations that are frequently used to calculate sound levels. Pref and I0 are reference pressure and intensity values which both correspond to near absolute silence (no pressure changes). Both equations are logarithmic and this is convenient because the sensitivity of the human ear is roughly logarithmic. One thing to note is that on Earth, the pressure changes that create sounds are very small compared to the overall atmospheric pressure. Here are the A-weighted RMS (root-mean-square) pressure levels of a few common sounds. Even the thin atmosphere on Mars supports the propagation of sound waves. But because of the lower pressure, the same sounds would have to be more intense (displace more atmosphere) for a typical microphone or human ear to hear them at the same level as here on Earth. For instance, given the same distance between the sound source and destination (say 10 feet), the reduction of pressure by a factor of 10 would reduce the sound pressure level by 20 dB. The reduction of pressure by a factor of 100 would reduce the sound pressure level by 40 dB. The pressure on Mars (at ground level) is approximately 100 times less than the atmospheric pressure on Earth (at sea level). Using the same reference pressure level of 2X10-10 bar, here are the equivalent Martian sound levels for the same common sounds on Earth. As you can see, for the same given distance, the sound of an electric table saw on Mars would be perceived as loud as a normal conversation here on Earth. So it is entirely possible to measure and record sound on Mars, but the scientific return is probably not high enough to justify flying the extra equipment weight. Will the Rover be coming back to Earth? Unfortunately, the rover does not come home unless someone goes to get it. The earliest (unmanned) Mars return mission is planned to be launched in 2005, and will not be returning the rover but rather soil and rock samples collected for a 2008 sample return mission. In the distant future, we may send astronauts to Mars for a scientific return mission. It is possible they may visit the Mars Pathfinder landing site and retrieve Sojourner and bring it back with them. If that is done, it will be studied in detail back on Earth, and take its place beside a similar rover at a National museum as a monument to past engineering and scientific achievements. Will the Rover collect soil samples? No, this rover cannot collect soil for return to Earth. Rovers in the year 2001 and 2003 are planned to navigate the Mars terrain for several kilometers and operate semi-autonomously for as long as a year, analyzing rocks and soil and collecting samples along the way. In 2005 it is envisioned that a sample return vehicle (with a small short range rover) will land close to either the 2001 or 2003 rover with its bounty of collected samples. The small rover will go get the samples held by the bigger rover and deliver it to the return vehicle. If all goes well we could have those samples safely delivered back to Earth in hermetically sealed containers by 2008 or early 2009. What kind of computer is in the Rover and Lander? The rover computer board uses an Intel 80C85 CPU that has been radiation hardened for class - S space flight applications. It runs on between 3 to 6 volts and has been designed to have a low power consumption. The rover has a total of 672 KBytes of memory, 160 Kbytes of which are non-volatile EEPROM. Of its total non-volatile memory, 48 KBytes is radiation hardened to protect it from SEU (Single Event Upset) latchups or damage. Not suprisingly, the code for the computer OS (Operating System) is stored in this area of memory. There is no hard disk in the rover for storing images or data, which are typically stored in a local buffer before being transmitted to the lander for downlink back to Earth. The CPU runs at a mind blowing speed of 2 MHz (0.1 MIPS - Million Instructions Per Second), which is significantly slower that many personal computers in use today. The source programs for the rover computer were written primarily in ANSI C and Assembler. C and Assembler for the Intel CPU is fairly easy to program and gives very good access to the low level instructions of the CPU. The rover computer is not as powerful as you might think, but it can easily get the job done of executing command sequences and controlling the rover. The lander computer on the other hand has much more computing horsepower. Its 32-bit RISC CPU and architecture is the derviative of a commercially available IBM 6000 computer. It executes at about 20 MIPS. The lander also does not have a hard disk, but has a rather large 128 MB of DRAM where data and images can be buffered for transmission back to Earth. Like the rover, it also uses radiation hardened components on its computer board. Where can I get a pair of 3D glasses? Sometimes they come with certain software programs. But if you can't find them locally, you can buy them from certain on-line companies. Reel 3-D Enterprises Inc. Click HERE to visit their web site. Another place you can go is Deep Vision 3D, they will give you a pair of 3D glasses is you send them a SASL letter with $2 to cover shipping. To find other companies that sell 3D glasses, do a web search on 3D glasses. The standard anayglyph has the left part of the image registered and colored red and the right part of the image is the green-blue part. When you wear your glasses, make sure the left eye has the red filter and the right eye the blue. Refers to the task that is currently running. Multi-tasking kernels need to know the identity of the currently running task so that it can know where to save the task's context when the kernel performs a task switch. For example, a global variable called ActiveTcb might point to the tcb of the active task. The kernel would save the active task's context on the active task's stack (i.e. the current stack) Another name for program. Designates the active state of an electrical signal. For example, raising a normally 0 volt CPU input control pin to 5 volts asserts the line. Similarly, pulling down a normally 5 volt CPU input control pin to 0 volts asserts the line. In either of the above examples, the line is said to be asserted. When an input put is asserted, it performs its function. For example, asserting the interrupt pin on the 8086 generates an interrupt. A task is blocked when it attempts to acquire a kernel entity that is not available. The task will suspend until the desired entity is available. A program breaks when a breakpoint is reached. A breakpoint is an instruction address at which a debugger has been commanded to stop program execution, and execute the debugger so that the user can enter debugger commands. At some point after breaking the user typically executes a debugger command like continue which continues program execution from the point at which the program was stopped. The process by which code repetitively checks for a condition. The following code exhibits busy waiting. The code stays in the loop until the condition is met. Contrast with event driven code. A CPU's context typically refers to all its registers (including IP and SP) and status register(s). When a task switch occurs, its context is saved, and the context of the next task to run is restored. The saving of the current task's context and the restoring of the context of the next task to run is called a context switch. In other words, a task will run forever, thus starving other tasks, unless it voluntarily gives up control. Control can be relinquished explicitly by a yield, pause, or suspend; or implicitly by waiting for an event. Contrast with priority based preemptive scheduling and time-slicing. A system is crashed when the CPU has halted to due a catastrophic error, however, the word can be used in the same sense has hung. See also the article A Cyclical Executive for Small Systems. Cyclical (also referred to as round robin) scheduling is done without a kernel per se by simply calling one task (typically a C function) after another in an infinite loop as shown below. In this scenario, each task must do its work quickly, save its state if necessary, and return back to the main loop. The advantages with this approach are small size, no kernel required, requires only 1 stack, and easy to understand and control. The disadvantages are no priorities, tasks must retain their states, more responsibility is placed on the programmer, and the possibility of excessive loop latency when there are many tasks. The term can have various meanings, but typically, a deadlock is a condition in which a task will remain forever suspended waiting for a resource that it can never acquire. Consider a system comprised of a keyboard and a display, with a separate semaphore for each. In order for a task to interact with the user it must acquire the console (keyboard and display) and therefore must acquire both the keyboard and the display semaphores. If more than one task decides to interact with the user at the same time a condition can arise where taskA acquires the keyboard semaphore and taskB acquires the display semaphore. Now taskA will wait forever for the display semaphore and taskB will wait forever for the keyboard semaphore. The solution in this example is to treat the keyboard and display as a single resource (console). Deadlocks can occur for a variety of reasons, but the end result is typically the same: the task will wait forever for a resource that it can never acquire. A method of managing timers such that only 1 timer count is decremented regardless of the number of pending timers. Consider 3 pending timers with durations of 50 ms, 20 ms, and 5 ms respectively. The timers are sorted and stored in a list with the lowest timer first: 5, 20, 50. Then the timer durations are replaced with the difference of the timer duration and the preceding duration, i.e., 5, 20, 50, is replaced with 5, (20-5), (50-20), which equals 5, 15, 30. This allows for only the first duration (5 ms in this example) to be decremented. Thus, when the first duration of 5 decrements down to 0, there are only 15 ms required to meet the 20 ms duration, and when the 50 ms duration is to be started, already 5 + 15 = 20 ms have expired, so only a count of 30 ms is required. This technique is attractive because the timer counts are typically decremented inside the timer isr and time inside any isr should be kept to a minimum. Refers to the transfer of executable code from a host to a target, typically using an RS-232 serial line. The target must have resident software (e.g., EPROM) that can read the incoming data, translate it if necessary (the file format may be ASCII hex for example) and load and run the code. If the target board has no resident software, then an ICE is required. The ICE connects to the host via RS-232 typically and accepts the download and loads the code into memory. Priorities that can be changed at run-time. Contrast with fixed priorities. Code that remains dormant until an event occurs. The following is an example. Until the message MOTOR_ON is received, the function waitMsg will suspend the current thread of execution until the message is received. The receipt of the message is the event that causes the ask to be scheduled and eventually run. Contrast with busy waiting. A software interrupt generated by the CPU when an error or fault is detected. A concept which dictates that if a task runs too long, its priority is lowered so that other tasks can get their fair share of the CPU. This concept is repugnant to real-time principles and is used chiefly in multi-user systems with the intent of disallowing one user to monopolize the CPU for long compiles and the like. A fault is an exception that is generated when the current instruction needs help before it can be executed. For example, supposed in a virtual memory system, a memory access is made to a page that is not in memory. In this case, a fault is generated which vectors to an isr that will read in the page. The fault exception is different in the way it returns from interrupt in that control is returned to the instruction that caused the fault, not the following instruction as with a normal interrupt. This allows the instruction to access the memory again, and this time succeed. For example, 3 pools are created as follows: pool 1 contains 100 64 byte blocks, pool 2 contains 50 256 byte blocks, and pool 3 contains 10 1K byte blocks. For example, if the user needed a 256 byte block of memory he would take a block from pool 2. Similarly, if he needed a 100 byte block he would still have to take a block from pool 2 even though bytes are wasted. The advantage is no fragmentation and high speed. Priorities that are set once, typically at compile time, and unalterable at run-time. Contrast with dynamic priorities. In a kernel or language which allows for memory allocation and deallocation, the available free memory can eventually become fragmented, i.e., non-contiguous, if memory is allocated from one large pool. For example after allocating and deallocating many different size blocks of memory, there may be 12K available, but it is not contiguous, and therefore, if software needs 12K it cannot use it, even though it is available. Contrast with fixed block memory allocation. A system is hung when it does not respond to external input (e.g. keyboard) but has not completely crashed. A hung system is typically spinning in an endless loop. Hard real-time refers to the strict definition of real-time. See real-time. The time it takes for the isr to be entered, once the processor interrupt pin is asserted. Intercept an interrupt by saving the current interrupt vector and writing a new interrupt vector. When the new interrupt service routine is finished it calls the old interrupt service routine before returning. See target. An electronic tool that allows for debugging beyond the capabilities of a standard software debugger. An ICE is essentially a hardware box with a 2 to 3 foot cable attached to it. At the end of the cable is a multi-pin connector connected to a CPU processor chip which is identical to the processor on the target board. The target processor is removed from your target board, and the connector plugged in. It also turns the host (a PC for example) into a debug station with supplied software. This allows for debugging of the target board even though the target does not have a keyboard, screen, or disk. A kernel owned task that is created and scheduled during system initialization. It runs at the lowest possible priority. The idle task has the lowest possible priority and runs only when no other tasks are scheduled. When any other task is scheduled, the idle task is preempted. The idle task is typically a do nothing tight loop. Its only purpose is to run when no other tasks run. It is a convenience mechanism in that no special kernel code is required to handle the case of all tasks being idle; the kernel sees the idle task like any other task. The key is that the idle task has a lower priority than any other task, and even though it is scheduled to run and occupies a place in the ready queue, it will not run until all other tasks are idle. And, conversely, when any other task is scheduled, the idle task is preempted. To start multiple instances of a task. Identical to hook except that the old isr is not called before returning. Most CPU's have a pin designated as the interrupt pin. When this pin is asserted the CPU (1) halts, (2) saves the current instruction pointer and the CPU flags on the stack, and (3) jumps to the location of an interrupt service routine (isr). Some CPU's have multiple interrupt pins, and each pin refers to a reserved memory location where the isr address can be found. For example, consider an imaginary 16 bit CPU with 8 interrupt pins I0 through I7, which refer to interrupt numbers 0 through 7, and an interrupt vector table starting at address 0. The addresses of the isr's for interrupt pins I0 through I7 would be located at addresses 0, 2, 4, 6, 8, 10, and 12. The dedicated interrupt pin approach works fine until more than 8 interrupts are desired. The 8086 handles this problem by relegating the handling of the interrupt signals with a special interrupt controller chip like the 8259A. The 8086 has only 1 interrupt pin. When the interrupt pin is asserted, the 8086 determines which device generated the interrupt by reading an interrupt number from the interrupt controller. Although the 8259A interrupt controller handles only 8 incoming interrupt lines, the 8259A's can be cascaded to handle more interrupts. Sometimes many interrupts are required. Most CPU's with dedicated interrupt pins have no more than 8 interrupt pins, so that no more than 8 incoming interrupts can be handled. A way around this problem is to handle interrupts with an interrupt controller chip. Typically interrupt controller chips can be cascaded so that even though a single interrupt controller chip handles only 8 incoming interrupts, a cascade of 4 can handle 32 incoming interrupts. The typical arrangement is for the hardware device to signal an interrupt by asserting one of its output pins which is directly connected to one of the interrupt controller's interrupt pins. The interrupt controller then signals the CPU by asserting the one and only CPU interrupt pin. If other interrupts are asserted while the CPU is handling an interrupt, the interrupt controller waits until it receives a signal from the CPU called and EOI (end of interrupt) before interrupting the processor again. The EOI signal is generally asserted with a software IO output instruction. Note however, that if more than one interrupt is generated on a single interrupt controller pin while the CPU is processing an interrupt, then all but the first waiting interrupt will be lost. For more information see interrupts. A routine that is invoked when an interrupt occurs. It is necessary to save the flags on entering an isr and restore them on exit, otherwise, the isr may change the flags, which can cause problems. The 8086 automatically saves flags on the stack prior to calling the isr, and restores the flags before returning using the IRET instruction. The reason for saving the flags can be understood by considering the following code fragment. The CMP instruction executes, and because AX = 0, the Z bit in the flags register is set by the CPU. Let us say that after the compare instruction, an interrupt occurs and the isr is jumped to which executes the following instruction. The above instruction will clear the Z bit because BX is not 0. The isr will eventually return to the instruction above JE LABEL7. If a standard RET instruction is used instead of IRET, the flags will not be restored, and the instruction JE LABEL7 will not perform the jump as it should because the Z bit was cleared in the isr. For this reason flags must be saved before entering an isr and restored prior to returning from an isr. Some CPU's handle interrupts with an interrupt vector table. On the 8086, the interrupt vector table is a 1K long table starting at address 0, which will support 256 interrupts numbered 0 through 255. The numbers 0 through 255 are referred to as vector numbers or interrupt numbers. The table contains addresses of interrupt service routines, which are simply subroutines that are called when the interrupt occurs. This means that an interrupt service routine must return with a special return instruction (IRET on the 8086) that pops the CPU flags off the stack and restores them before returning. An interrupt service routine can be invoked by software using the 8086 INT instruction, which in addition to the return address pushes the CPU flags onto the stack. An interrupt service routine can also be invoked by software using a standard CALL instruction as long as the CPU flags are first pushed onto the stack. For details of how an interrupt is generated and serviced, see interrupts. Software that provides interfaces between application software and hardware and in general controls and programs all system hardware. A multi-tasking kernel provides for multi-tasking in the form of threads, processes, or both. A real-time kernel provides is a multi-tasking kernel that has predictable worst case tasks switch times and priorities so that high priority events can be serviced successfully. Refers to a kernel, or a very minimal kernel used for debugging, typically written by the programmer for a particular project, or supplied on ROM from the board manufacturer. A data structure that is passed to a task by placing a pointer to the structure into a queue. The queue is a doubly linked list that is known to the task. Kernel functions are supplied to remove messages from the queue. A technique by which the sender of the message changes the sender field in the message structure before sending it so that the receiver of the message will reply to a task other than the actual sender. Messages are queued, mail is not. New mail data may optionally over-write old, which is advantageous for certain applications. Consider taskA that updates a display and receives the latest data on a periodic basis from taskB. Assume further that the system is busy to the point that taskB sends taskA two data updates before taskA can resume execution. At this point the old data is useless, since taskA only displays the latest value. In this situation, queuing (using messages) is undesirable, and mail with over-write enabled is preferred. A term that describes an environment in which 2 or more CPU's are used to distribute the load of a single application. A term that describes an environment in which a single computer is used to share time with 2 or more tasks. Contrast with multi-user. A term that describes a single computer that is connected to multiple users, each with his own keyboard and display. Also referred to as a time-sharing system. Contrast with multi-tasking. Non-reentrant code cannot be interrupted and then reentered. Reentrancy can occur for example, when a function is running and an interrupt occurs which transfers control to an interrupt service routine, and the interrupt service routine calls the function that was interrupted. If the function is not reentrant, the preceding scenario will cause problems, typically a crash. The problem typically occurs when the function makes use of global data. To illustrate, consider a function that performs disk accesses and uses a global variable called NextSectorToRead, which is set according to a parameter passed to the function. Consider the following scenario. The function is called with a parameter of 5, and NextSectorToRead is set to 5. The function is then interrupted and control is transferred to an isr which calls the function with a parameter of 7, which results in NextSectorToRead being set to 7. A unit of memory, typically around 4K, which is used as the smallest granule of memory in virtual memory systems. Preemption occurs when a dormant higher priority task becomes ready to run. A dormant higher priority task can become ready to run in a variety of ways. If time-slicing is enabled, (which is rarely the case in properly designed real-time systems), preemption occurs when the task's time-slice has expired. Preemption can also occur when a message is sent to a task of a higher priority than the active task. Consider the following. The active task, taskB, invokes waitMsg to wait for a particular message named START. Since the message START is not in taskB's message queue (i.e., no task has sent taskB a message named START), taskB will be suspended until such time as the message START is received. Once taskB is suspended, the highest priority waiting task, (the task at the front of the ready queue), is removed from the ready queue, (call it taskA which is of a lower priority than taskB) and it becomes the new active task. Now if taskA sends taskB a message named START, the kernel will suspend taskA, and resume taskB since taskB has a higher priority and it now has a reason to run (it received the message that it was waiting for). Task preemption can also occur via interrupt. Preemption occurs when the current thread of execution is stopped and execution is resumed at a different location. Preemption can occur under software (kernel) or hardware (interrupt) control. See priority based preemptive scheduling. A number attached to a task that determines when the task will run. See also scheduling. A multi-tasking scheduling policy by which a higher priority task will preempt a lower priority task when the higher priority task has work to do. Contrast with cooperative scheduling and time-sliced scheduling. An inappropriately named condition created as follows. Consider three tasks: taskA, taskB, and taskC, taskA having a high priority, taskB having a middle priority, and taskC having a low priority. Now, taskB can run until it decides to give up control, creating a condition where a high priority task (taskA) cannot run because a lower priority task has a resource that it needs. This situation can be avoided by proper coding, or by using a server task instead of a semaphore. A process is a single executable module that runs concurrently with other executable modules. Processes are separate executable, loadable modules as opposed to threads which are not loadable. Multiple threads of execution may occur within a process. For example, from within a data base application, a user may start both a spell check and a time consuming sort. In order to continue to accept further input from the user, the active thread could start two other concurrent threads of execution, one for the spell check and one for the sort. The range of memory that a program can address. Through a scheme involving special hardware and kernel software, tasks can be restricted to access only certain portions of memory. If a program attempts to jump to a code location outside its restricted area or if it attempts access data memory outside its restricted area, a protection violation occurs. The protection violation generates an interrupt to an interrupt service routine. The interrupt service routine will typically terminate the task that caused the violation and schedule the next ready task. When the interrupt service routine returns, it returns to the next ready to run task. Special hardware is needed because each code or data access must be checked. This is essentially done through special hardware tables that kernel software can write to. For example, when a task is created, the kernel assigns a hardware table to the task (each task is assigned a different hardware table). The kernel then writes into this table the lower limit and the upper limit of memory that the task is allowed to access. Each time the task accesses memory, the memory management hardware checks the requested memory access against the allowable limits, and generates a protection violation if necessary. In a strict sense, real-time refers to applications that have a time critical nature. Consider a data acquisition and control program for an automobile engine. Assume that the data must be collected and processed once each revolution of the engine shaft. This means that data must be read and processed before the shaft rotates another revolution, otherwise the sampling rate will be compromised and inaccurate calculations may result. Contrast this with a program that prints payroll checks. The speed at which computations are made has no bearing on the accuracy of the results. Payroll checks will be generated with perfect results regardless of how long it takes to compute net pay and deductions. See also hard real-time and softRealtime. A real-time kernel is a set of software calls that provide for the creation of independent tasks, timer management, inter-task communication, memory management, and resource management. A real-time kernel plus command line interpreter, file system, and other typical OS utilities. The process by which the active task voluntarily gives up control of the CPU by notifying the kernel of its wish to do so. A task can voluntarily relinquish control explicitly with a yield, pause, or suspend; or implicitly with waitMsg, waitMail, etc. When the active task relinquishes control, the task referenced by the node at the front of the ready queue becomes the new active task. The ready queue is a doubly linked list of pointers to tcb's of tasks that are waiting to run. Describes a task switching policy that is consistent with real-time requirements. Specifically, when ever it is determined that a higher priority task needs to run, it runs immediately. A resource is a general term used to describe a physical device or software data structure that can only be accessed by one task at a time. Examples of physical devices include printer, screen, disk, keyboard, tape, etc. If, for example, access to the printer is not managed, various tasks can print to the printer and inter-leave their printout. This problem is typically handled by a server task whose job is to accept messages from various tasks to print files. In this way access to the printer is serialized and files are printed in an orderly fashion. Consider a software data structure that contains data and the date and time at which the data was written. If tasks are allowed to read and write to this structure at random, then one task may read the structure during the time that another task is updating the structure, with the result that the data may not be time stamped correctly. This type of problem may also be solved by creating a server task to manage access to the structure. To run again a suspended task at the point where it was suspended. Formally, the process by which the active task is suspended and a context switch is performed to the previously suspended task. A multi-tasking scheduling policy in which all tasks are run in their turn, one after the other. When all tasks have run, the cycle is repeated. Note that various scheduling policies are run in round robin fashion, e.g., cooperative scheduling, time-sliced scheduling, and cyclical scheduling. Cyclical and round robin scheduling are used inter-changeably. A scheduling mechanism that allows for creating and scheduling new tasks and changing task priorities during execution. See also scheduling and static scheduling. A scheduling mechanism in which all tasks and task priorities are described and bound at compile-time; they cannot be changed during execution. See also scheduling and dynamic scheduling. There are various scheduling policies: see cooperative scheduling, time-sliced scheduling, cyclical scheduling, and priority based preemptive scheduling. For software implementation details see the article HowTasks Work - Part 2. This term has several meanings. In its simplest form, it is a global flag - a byte of global memory in a multi-tasking system that has the value of 1 or 0. In multi-tasking systems, however, a simple global flag used in the normal manner can cause time dependent errors unless the flag is handled by a special test and set instruction like the 80x86 XCHG instruction. A semaphore is typically used to manage a resource in a multi-tasking environment - a printer for example. See XCHG for a code example. In another form, the semaphore may have a count associated with it, instead of the simple flag values of 1 or 0. When the semaphore is acquired, its count is made available to the caller, and can be used for various purposes. For example, consider a system that has three printers numbered 1, 2, and 3. A semaphore could be created with a count of 3. Each time the semaphore is acquired its count is decremented. When the count is 0, the semaphore is not available. When the semaphore is released, its count is incremented. In this way a pool of 3 printers can be shared among multiple tasks. The caller uses the semaphore count value returned to him to determine which numbered resource (e.g., printer 1, 2 or 3) he is allowed to use. Numerous variations on this theme can be created. Compare also with server task. The advantage of a server task is that it does not block the calling task if the resource is not available, thus avoiding priority inversion. In general semaphores should be avoided; furthermore they are unnecessary in a message based OS. The server task approach to resource management is preferred over semaphores as it is cleaner and less error prone. A piece of test equipment that is connected in series with a serial line for the purpose of displaying and monitoring two way serial communications. The term usually refers only to RS-232 serial lines. A task dedicated to the management of a specific resource. A server task accepts requests in the form of messages from other tasks. For example, a server task could be created to manage access to a single printer in a multi-tasking environment. Tasks that require printing send the server a message that contains the name of the file to print. Note that the server task approach does not block the calling task like a semaphore will when the resource is unavailable; instead the message is queued to the server task, and the requesting task continues executing. This is an important consideration when is a concern. A debugger command that allows an application program to execute one line of the program, which can either be a single assembly language instruction, or a single high level language instruction. There are typically two distinct single step commands - one that will single step into subroutine calls, and one that will step across them (i.e., enter the routine, but do not show its instructions executed on the screen). The latter command is useful, otherwise many unwanted levels of subroutines would be entered while single stepping. Refers to applications that are not of a time critical nature. Contrast with hard real-time. See also real-time. The process by which a software system is put under heavy load and demanding conditions in an attempt to make it fail. Refers to the scenario in which subroutineA calls subroutineB which calls subroutineC... which calls subroutineN. This is relevant for real-time systems because if a task is 7 subroutines deep, and the task is preempted, the return addresses must be preserved so that when the task is later resumed, it can return back up the chain. The most practical way of preserving subroutine nesting is by assigning each task its own stack. This way when a task is resumed, the SP is first set to the task's stack. The immediate cessation of a thread, preceded by the saving of its context. Sometimes one task must wait for another ask to finish before it can proceed. Consider a data acquisition application with 2 tasks: taskA that acquires data and taskB that displays data. When taskA runs and updates the data structure, it sends a message to taskB which schedules taskB to run, at which time it displays the new data, then again waits for a message, and the scenario is repeated. The target is the actual product on which the software is to run. In most common situations, the host and target are the same. For example, a word processor is developed on the PC and runs as a product on the PC. However, for various real-time and embedded systems, this is not the case. Consider a small single board computer that controls a robot arm. The software cannot be developed on this board because it has no keyboard, display, or disk, and therefore, a host computer, like a PC, is used to develop the software. At some point, when it is believed that the software is ready for testing, the software is compiled and linked to form an executable file, and the file is downloaded to the target, typically over an RS-232 serial connection. Debugging on the target is typically done with an ICE. A thread of execution that can be suspended, and later resumed. Data corruption due to preemption. Consider a preemptive multi-tasking environment in which tasks use a global flag to gain access to a printer. The following code will not have the desired effect. Since the 80x86 flags register (not to be confused with the flag variable), which contains the status of the last CMP instruction, is restored with the original task's context, the JE will fail, since the flag was 0. The original task will now acquire the flag and interleave its printing with the other task's printing. This problem can be overcome by using a read-modify-write instruction like XCHG or by managing the printer with a server task. Time dependent errors can also occur when preemption is used with non-reentrant code. Consider a graphics library that is non-reentrant. If taskA calls a non-reentrant graphics function and is then preempted to run taskB which calls the same function, errors will result. Sometimes the non-reentrancy problem is not so obvious. Consider a non-reentrant floating software library that is called invisibly by the compiler. For example the statement y = 5.72 * x; would make a call to the floating point software multiplication function. If the current task were preempted while executing this instruction, and the next task to run also performed a multiply, then errors can again result. Note that floating point time-dependent errors could still occur even if a floating point chip was used instead of a floating point software library. An error could occur if the floating point CPU's context context is not saved by the kernel. A task control block is a data structure that contains information about the task. For example, task name, start address, a pointer to the task's instance data structure, stack pointer, stack top, stack bottom, task number, message queue, etc. A single generic task can be created to handle multiple instances of a device. Consider 5 analog channels that must be read and processed every n milliseconds, the number n being different for each channel. A generic task can be created that is channel independent through the use of instance data. Instance data is a C structure that in this case contains information specific to each channel, i.e., the IO port address of the channel, the sample interval in milliseconds, etc. The task code is written so that it references all channel specific data through the instance data structure. When the task is created a pointer to the task's task control block is returned. One of the fields of the task control block is a user available void pointer named ptr. After starting the task, the ptr field is set to an instance data structure that contains the sample rate, IO port address, etc. The code above shows what the generic task might look like. For further information see the example. A thread is a task that runs concurrently with other tasks within a single executable file (e.g., within a single MS-DOS EXE file). Unlike processes, threads have access to common data through global variables. A sequence of CPU instructions that can be or have been executed. Each task is allotted a certain number of time units, typically milliseconds, during which it has exclusive control of the processor. After the time-slice has expired the task is preempted and the next task at the same priority, for which time-slicing is enabled, runs. This continues in a round-robin fashion. This means that a group of time-sliced high priority tasks will starve other lower priority tasks. For example, in a 10 task system, there are 3 high priority tasks and 7 normal priority tasks. The 3 high priority tasks have time-slicing enabled. As each high priority task's time-slice expires, the next high priority task is run for its time slice, and so on. The high priority tasks are run forever, (each in its turn until its time-slice expires), and the low priority tasks will never run. If however, all high priority task waits for an event, (for example each pauses for 100 ms), then lower priority tasks can run. The behavior of tasks in a time-slicing environment is kernel dependent; the behavior outlined above is only one of many possibilities, but is typically the way time-sliced tasks should behave in a real-time system. Some kernels may implement the concept of fairness to handle this situation, however, fairness is repugnant to real-time principles as it compromises the concept of priority. An ICE command that will save the most recent n instructions executed. The trace can also be conditional, e.g., trace only those instructions that access memory between 0 and 1023. A buffer in ICE memory that stores the last n instructions executed. It is useful while debugging as it shows a history of what has occurred. A technique in which a large memory space is simulated with a small amount of RAM, a disk, and special paging hardware. For example, a virtual 1 megabyte address space can be simulated with 64K of RAM, a 2 megabyte disk, and paging hardware. The paging hardware translates all memory accesses through a page table. The 80x86 XCHG instruction can be used in a single processor, preemptive multi-tasking environment to create a flag that is shared between different tasks. A simple global byte cannot be used as a flag in this type of environment since tasks may collide when attempting to acquire the flag. The XCHG instruction is typically used as follows. Fflag is a byte in RAM. If the flag is 1, then the XCHG instruction will place a 1 into AL. If AL is 1 after the XCHG then the loop must continue until finally AL is 0. When AL is 0 the following is known: (1) The flag was 0 and therefore available, and (2) the flag is now 1 due to the exchange, meaning that the flag has been acquired. The effect is that with a single instruction, a task can check, and possibly acquire the flag, thus eliminating the possibility of a collision with another task. This is typically done with the 80x86 LOCK instruction as shown below. Note that the LOCK instruction provides information only. It is up the shared RAM bus arbitration logic to incorporate the LOCK* pin into its logic. Newer processors like the 80386 incorporate the LOCK implicitly into the XCHG instruction so the LOCK instruction is unnecessary. The process by which a task voluntarily relinquishes control (via a kernel call) so that other tasks can run; the task will run again in its turn (according to its priority). The choice of server platforms can be difficult for managers who do not have highly specialized training in systems and network administration. In this paper, Microsoft Windows NT Server is compared to UNIX, a large family of commercial and non-commerical operating systems with a common heritage and many similarities. The main focus of the comparison is on the areas of functionality, reliability, system management, and performance. This paper is about servers, not workstations. Other factors, such as product pricing, quantity and quality of bundled software, and a section on common misconceptions about both groups of operating systems are presented to provide a more complete view of these products. The purpose of this page is to provide corporate managers with the information they need to make intelligent purchasing decisions relating to server hardware and software. This information is based on the experience of seasoned MIS professionals. The intent is to compare and contrast the implications of choosing one operating system over another in non-technical terms, or at least with as little technical jargon as possible. Due to the urgent need for the information presented here, this page is being released prematurely and should be considered a work in progress. Anyone wishing to contribute to this project is welcome to send me e-mail. Please confine your e-mail to constructive comments or criticism. Most managers will agree that the mere cost of an operating system is trivial when looking at the big picture. In order to match the functionality of a BSDI installation, additional Microsoft products and third-party solutions would bring the final price of a comparable NT solution to around $4,000, according to BSDI. Here one sees that successful marketing can often distract customers from considering their need for functionality. NT is often chosen for budget reasons since many customers are not willing to pay for the more expensive hardware required by most commerical flavors of UNIX. More important, however, is the overall cost of implementation which includes system administration along with several other factors like downtime, telephone support calls, loss of data due to unreliability, etc. Tippett Studio, the company responsible for the graphics in Starship Trooper, which received an Oscar nomination for Best Special Effects, uses 130 SGI ( Silicon Graphics, Inc.) machines running IRIX, SGI's very own UNIX operating system. For the most cost-conscious customer, Linux, FreeBSD, NetBSD, or OpenBSD would be the obvious choices. They cost nothing, yet they are just as stable and offer as much functionality as, if not more than, the commercial UNIX operating systems. One reader informed me that mentioning Linux would detract from the credibility of this article. I beg to differ. The existence of such alliances as mentioned in the article Andreessen Sees Mozilla-Linux Upset of Windows clearly shows that Linux is strengthening its presence in commerical environments. The recent trend among some corporations is to use these cost-effective operating systems. Hewlett-Packard used Linux instead of its own HP-UX UNIX operating system while developing its new PA-RISC processor architecture. Schlumberger will be marketing a remote telephony solution that incorporates Linux. It is interesting to note that SunWorld On-Line gives Linux positive press in one of its articles, Linux lines up for the enterprise. Since these operating systems are free for use even in commercial environments, many ISPs run on Linux or FreeBSD. NetBSD will run on practically anything: DEC Alpha, Motorola 68k (Amiga, Atari, Mac, MVME, Sharp, Sun3), PowerPC, Intel, DEC VAX, Acorn RISC, MIPS (Sony NEWS, DECstation), etc. OpenBSD's primary focus is on correctness and security. Linux is the most popular and will run on wide range hardware: Sun, Intel, DEC Alpha, PowerPC, PowerMac, etc. Currently, Linux is perhaps the fastest growing operating system on the market. For more information, see Linux Resources or Red Hat Software. Yesterday's college students learned their UNIX expertise on Linux and FreeBSD. Today they're working in IT departments, and many of them are openly hostile to both Microsoft and Windows NT. As a result, Linux, BSD, Solaris, and other forms of UNIX are finding their way into IT departments, both overtly and on the sly. For example, are you sure that's an NT server you're connecting to at work? IS employees in many corporations have secretly installed UNIX servers that provide native NT services. Why take such a risk? Linux and FreeBSD are free, as is SAMBA, the software that provides NT services. So the IS department saves money. And managers are unlikely to find out UNIX is behind the scenes because fewer people will complain about server downtime. Fewer people will complain because the servers are more stable than Windows NT. Linux, FreeBSD, and BSDI UNIX outperform Windows NT by a wide margin on limited hardware, and under some circumstances can perform as well or better than NT on the best hardware. Once behind in scalability features, UNIX on Intel is catching up and may soon surpass NT in the number of processors it can use, and how it uses them. Nicholas Petreley, The new UNIX alters NT's orbit: The re-emergence of UNIX threatens to modify the future direction of NT, NC World, April 1998. What can you expect from Windows NT Server out of the box and from UNIX out of the box? NT can communicate with many different types of computers. So can UNIX. NT can secure sensitive data and keep unauthorized users off the network. So can UNIX. Essentially, both operating systems meet the minimum requirements for operating systems functioning in a networked environment. Put briefly, UNIX can do anything that NT can do and more. NT is often considered to be a multi-user operating system, but this is very misleading. The NT user cannot just run any application on the NT server (in order to take advantage of the superior processing power of server hardware). This also includes graphics-based applications since X-server software is standard issue on all UNIX operating systems. With Windows NT, you will have to buy a separate software package in order to set up an e-mail server. UNIX operating systems come with a program called Sendmail. There are other mail server software packages (or MTAs, Mail Transport Agents) available for UNIX, but this one is the most widely used, and it is free. Some UNIX administrators feel that exim or qmail are better choices since they are not as difficult to configure as sendmail. Both exim and qmail, like sendmail as well, are free for use even in a commercial environment. Many NT-based companies use Microsoft Exchange Server as their MTA. This is an expensive solution with limited success in an enterprise environment. Microsoft Exchange Server Enterprise Edition - 25 Client Access Licenses costs $3,549.00. Although NT provides basic password security, it only provides file-level security if you choose to use its proprietary file system called NTFS. Some MIS departments are reluctant to implement this file system (at least on users' machines), because they feel that recovering from disk problems is hindered by the use of NTFS. It is a common belief that NTFS formatted drives cannot be read by DOS, an important OS in the recovery from such problems. Rune Knapstad informed me of a DOS utiltiy called NTFSDOS which can mount NTFS partitions. It is interesting to note that this is a third-party product and not a Microsoft one. More important than this issue, however, is that NT does not provide any mechanism for limiting a user's disk usage! UNIX and Novell, on the other hand, provide software for performing this seemingly elementary control. Microsoft has announced, however, that its not yet released NT Server 5.0 will provide new storage management features such as disk quotas. To summarize, once you logon to an NT network, all you can do is read files and print. In a UNIX environment, once you log in to a UNIX server, you can be on that machine and do anything on it that you could do if you were sitting at its keyboard. With NT, don't plan on being able to set up an e-mail server with the software at hand. You will need to buy expensive mail server software like Microsoft Exchange Server separately. If your NT server should function as a file server - what else can you do with it really? Ease of configuration and being able to configure a server without causing downtime is yet another aspect of functionality. Some versions of UNIX (Linux, for example) support loadable device modules. This means you can boot Linux and reconfigure its support for hardware and software on the fly. For example, you can boot Linux without support for the SCSI card you have installed. You simply load support for that SCSI card when you need to access one or more of the SCSI-connected devices, such as an optical disk for backup. You can unload the SCSI driver when you're finished. You can also freely load and unload support for sound cards, network cards -- even filesystems such as HPFS, FAT, VFAT, and others (an NTFS driver is in the works). Any UNIX with loadable module support is therefore by nature more appropriate for a server environment because almost all configuration changes do not require system restarts. Windows NT doesn't even come close. Even insignificant changes to a Windows NT configuration require or request a shutdown and reboot in order to make the changes take effect. Change the IP address of your default gateway and you need to reboot. You can't even change the type of modem you use for a dial-up PPP connection without a reboot to update the system. None of these limitations exist in UNIX. Nicholas Petreley, The new UNIX alters NT's orbit: The re-emergence of UNIX threatens to modify the future direction of NT, NC World, April 1998. When it comes to more sophisticated networking functionality, it seems that Microsoft's NT Server 4.0 Enterprise Edition can't hold a candle to the more mature commercial UNIX operating systems. Although not essential to network performance, 64-bit computing is here today with these UNIX operating systems (as opposed to NT's 32-bit operating system). D.H. Brown Associates Inc. However, Digital UNIX lacks advanced NFS features such as CacheFS and AutoFS. IRIX 6.4 places third, bundling CacheFS and AutoFS, and network security features almost as strong as Digital's. However, Sun relies on its own Web server, rather than Netscape, Microsoft or Apache, and lacks authoring tools as well as important services such as Novell's NDS directory service. HP provides strong Internet support within HP-UX, bolstered by its good showing in advanced Internet protocol function and network security, while lagging behind in support for advanced NFS capability. HP-UX, along with AIX, has also established a lead in supporting NDS. Microsoft has largely focused adding value to its bundled Web server product and to tuning its Java Virtual Machine. In today's world, reliability is often more important than speed. Although performance is largely a function of hardware platform (see the next section), it is in the area of reliability that the choice of operating systems has the most influence. The analogy of a fast, economical automobile with lots of gadgets, and sporty appearance that frequently stalls in traffic despite repeated visits to the authorized service center is actually quite representative of Windows NT. One often hears about Windows NT Server being referred to as a stable operating system, but this is not entirely accurate. Were it so, then we wouldn't be reading articles like Corporate IT needs an engine that never quits (Peter Coffee, PC Week 3-30-98) or We do not have a failure to communicate (Peter Coffee, PC Week 04-13-98). When the author of these two articles posed the question, What do you use when failure is not an option? he was bombarded by three times the usual number of vigorous e-mail replies. Concerning these replies he states. Notably, I did not get a single message from anyone who took the position that Windows NT was good enough. Quite the opposite: Several messages expressed a resigned expectation that Windows NT 5.0 would stagger out the door, burdened with immature add-on services but without achieving corporate-class reliability in its basic functions. I heard from one reader who said that at his site, Linux on a 486 is outperforming Windows NT on a 200MHz Pentium, and he has Linux machines that have been running without interruption since before Windows NT 4.0 was released. I also heard from enterprise-class sites where Linux is considered a proven choice, with source-code accessibility outweighing the dubious advantage of more traditional vendor support. What others promise someday, Linux gives many users now--at a bargain price. Peter Coffee, We do not have a failure to communicate, PC Week, 4-13-98. Indeed, Windows NT is a great improvement over Windows 3.1 or Windows 95, but it still has a long way to go before it can reach the level of stability offered by even the freeware UNIX operating systems. Windows NT's lack of stability is a known issue yet managers tend to deal with it in discrete ways, reports one IT professional. NT is known to crash too frequently for many IT manager's tastes. These companies aren't inclined to talk about their decisions 'because of pressure from upstairs,' Flynn says. Mark Gibbs, Lookin' into Linux, Network World, March 30, 1998. The only method of recovery in this situation is powering the machine off and rebooting. What causes blue screens in NT varies. In my own experience, the following can induce this state of failure. This list is by no means complete. As a matter of fact, Tim Newsham, a software developer for both Windows and UNIX platforms, found this short list very misleading. In the BSOD section you mention a few ways that a BSOD can be caused. I think this (small) list is misleading to the reader. There are so many ways that an NT system can crash, that by listing a small number you are likely to give the wrong impression. More dangerous yet is the fact that your cases mostly involve a person who is on the console doing something BAD to cause a crash. The Blue Screen of Death can be commonplace in some computing environments and is often difficult to troubleshoot due to the either cryptic or non-existent error reporting. In addition to this, NT is particularly prone to virus attacks on the Intel-based hardware. For operating systems on Intel hardware that must be booted from a hard drive, i.e. NT Server, the Master Boot Record of a hard drive can be the death of the operating system. Linux, along with several other UNIX operating systems that run on Intel-based hardware, can load a compressed kernel from a boot floppy, thus avoiding this problem. What this means is, an NT Server can theoretically be crashed by a virus written 10 years ago for MS-DOS computers. Anyone planning to deploy an NT Server in a mission critical environment should consider this fact. I personally have encountered MBR viruses in a corporate environment running Windows NT 4.0 (no Windows 95 clients!), and their effects are devastating. In addition to this, most viruses that would incapacitate a Windows operating system don't have an effect on UNIX operating systems since they often require the MS Windows environment to do their damage. The system was crashing two to three times a day with no reason that I could find. I was on the phone with Microsoft and Cats constantly, but nobody could figure it out. Fourteen months later, we are running Linux as our server. The UNIX equivalent of the Blue Screen of Death would be called kernel panic. It obviously exists, since I have heard and read about it, but I've never been witness to it in my professsional career. Although I am sure that UNIX servers do crash on occasion, these are extremely rare events. If and when a UNIX server crashes, it is almost always due to a hardware failure of some sort. If none of the above the above occurs, then a UNIX system's uptime can be measured in years. NT, however, cannot boast of such periods of uninterrupted service. Even if one could eliminate the Blue Screen of Death, NT is hampered by its own design and use of difficult-to-recreate proprietary binary configuration files, for instance, the NT registry. Read about a massive NT failure that lead to over 10,000 NT machines being rendered useless for any task requiring network resources. The argument that Windows NT is easier to manage due to its GUI (point-and-click graphical user interface) is unfounded. The advantage, if any, of GUI over CLI (command line interface, i.e. having manually to type commands from a keyboard) is questionable. The first assumption is that Windows NT has an advantage over UNIX because of its GUI. This is wrong. UNIX operating systems have a GUI as well (see this graphic example). NT has long enjoyed an intuitive user interface for managing single systems, largely benefiting from the exceptional familiarity of the Windows look-and-feel adopted by the NT GUI. NT currently enjoys none of these features. Processing power is largely a function of computer hardware rather than of operating system. Since most commercial UNIX operating systems run only on high-end workstations or servers, it would be ridiculous to compare an IBM SP2 or a Sun Enterprise 10000 to anything Compaq or Dell produces. UNIX has been historically an operating system for high-end hardware. To say that UNIX outperforms NT based on the results of differing hardware would be unfair to Microsoft. On the other hand, Microsoft has reduced, rather than increased, the number of hardware architectures it supports. NT for MIPS has been discontinued due to lack of customers and PowerPC support is only marginal. NT, now reduced to only x86 and Alpha architectures will remain a poor man's server as it is commonly referred to in the IT business. To be fair, one should compare NT Server's performance to that of Linux or FreeBSD, since all three operating systems run on the same hardware. Unfortunately, a truly objective analysis of performance would have to based on benchmarks, but these are not plentiful and usually only focus on specific areas like Web performance: Caldera OpenLinux vs. Windows NT: WebBench Performance Test. The general consensus among IT professionals is, however, that Linux and FreeBSD greatly outperform NT. Considering that these UNIX kernels are custom-compiled to contain only the software actually required by the administrator, Linux and FreeBSD can function more efficiently than NT. Inherently, any operating system requiring fewer resources will outperform a more bloated operating system like NT. UNIX does not require a graphical user interface to function. NT does. Anyone knows that graphics require incredible amounts of disk space and memory. The same holds true for sound files, which seem to be so important to the Microsoft operating systems. Benchmarks performed on similar UNIX operating systems using the same hardware are more meaningful. Net Express, an Internet retailer of x86-based hardware, whose systems are designed for scientists, engineers and the telecommunications industry, shows what results can be achieved with the proper operating system. Tests were conducted on Pentium 133MHz machines with 32MB's of RAM, the Triton-II 430HX chip set and a BusLogic SCSI controller. For NT, the test results were pretty devastating. Telenet System Solutions produced the most surprises during our tests, with a BSDi-powered, single-CPU system that kept up with-and in some cases outperformed-twin-CPU machines running Windows NT. The differentiating factor here was the BSDi 3.0 OS loaded on the machine and its Apache HTTP server software. As of April 1998, the best SPECWeb result overall is 7214 http operations per second on an 8-CPU Silicon Graphics Origin 2000 server running IRIX 6.5 and a Netscape Web server. For more details, see SGI's press release. Admittedly, the following example might not be the most scientific test of performance difference between Linux and NT, but Richard Betel's information does give one an idea of what one can expect in real-life situations. I've been running the distributed.net RC5 cracking client for about 2 months now. It's installed on every server that has a significant amount of idle time. This includes two identical machines: Both are Dual-Pentium II at 300MHz, 128MB RAM. One is running NT, and has an idle exchange server (we're planning to offer a service on it, but at the moment, its totally idle), and the other is running Linux (we're putting that one through its paces. Its a Samba server, and we're recompiling all kinds of things on it). The Linux box is trying keys at 1.8 times the speed of the NT box. For an operating system that has evolved from a toy operating system, it offers some professional functionality. Although it does not scale very well -- performance goes down with more than 4 CPUs per server -- it has come a long way. Although I would not recommend it as the primary operating system in an enterprise environment, it should yield satisfactory performance for small businesses with fewer than 250 user accounts that do not run mission critical processes. By converting everything to Windows NT a company can eliminate the problems of a heterogeneous networking environment. The first assumption here is that a heterogeneous networking environment is a problem. I once worked at a company where NT and Novell coexisted with very little conflict. As a matter of fact, the very reason for this coexistence was because Novell outperformed NT in the area of file and printer sharing services. With UNIX, one can create Microsoft-compatible file and printer sharing without the users ever knowing that these services emanate from a UNIX server. For all they know, it's an NT server. This functionality is provided for in Sun's UNIX operating system, Solaris. Linux can use a software package called Samba that ships with most distributions to achieve this. And, once again, it's free. UNIX is this outdated, cryptic, command-line based operating system. Wrong! TWM is the predecessor of the various FVWM window managers which also ships with Linux. If you've never had the opportunity to sit at a computer running UNIX, here are some SCREENSHOTS of these window managers: CDE, TED (TriTeal's CDE for Linux), KDE, FVWM 1.24, FVWM 2.x, FVWM-95, olvwm(OpenLook Virtual Window Manger). These are only some of the GUI interfaces available to UNIX users. Matt Chapman's Guide to Window Managers for The X Window System is an excellent resource on this topic. You will find many more screenshots on his site than I am able to list here. Keep in mind that almost all of these window managers are highly configurable; you shouldn't be surprised to see screenshots made of the same window manager which look completely different. As Matt states on his page, Let's face it, people are different, and those that use computers use them in different ways for different tasks. So why do some think we should all use (suffer?) the same interface? Ironically, it is Microsoft's graphical user interface that is lacking the features of customization. As for the claim that UNIX is behind the times, it is still the operating system of choice for science, engineering, research, and higher education. Most engineers would choose UNIX over NT without hesitation. They are fully aware of its ability to be customized and its tuning capabilities for the optimization of specialized computing tasks. Everyone is converting to NT anyway, we might as well gradually replace our UNIX servers with NT servers. It's the way of the future. If you talk to MIS managers of some large corporations who had UNIX and Novell two years ago, and then replaced their Novell servers with NT servers, you'll find that none of them can manage without their UNIX servers. It seems that heavy processing is still better accomplished with UNIX servers. So far in my career, every Oracle server I've ever seen was running on a UNIX server. One IT professional, however, did send me e-mail saying, I support several installations of ORACLE on NT. There are performance and functional issues that I encounter which I have never seen on UNIX (Pyramid). It will be a long time before you hear me praise NT or any other MS product. I believe that Gates and his empire have done more to lower the standards of our society than anything else in my lifetime. If my product had the same quality as theirs, airplanes would be falling out of the sky hourly. At my day job I work at a big firm. It's one of the biggest of its kind in the world. We decided to go with a Network Monitoring and Management package from Cabletron. It's available on both NT and Unix. The people who would run it gave them a blank check for the system to be set up under NT because they were more familar with NT than they were with Unix. About a year and a quarter million dollars later, they finally gave up on NT and did it over with Solaris. NT just doesn't scale up. I develop software in NT and in UNIX. I despise NT. It is a horrid beast, it performs very very poorly and it is way too unstable. Some parts of NT are so broken that the majority of time porting software to the system involves working around microsoft bugs. It bothers me that so many people are migrating away from unix to NT. I can only imagine that eventually there will be a large anti-NT backlash as management types realize how much NT has hurt their organizations. Jesse: I'm sure Microsoft, like IBM in the 60s, would love to have people believe that choosing something other than their products would be a career-limiting move. But it just ain't so! I'd fire an employee for putting mission-critical e-mail or Web server applications on an NT machine rather than a UNIX box. We use FreeBSD for everything and there is nothing more stable. Not only are free UNIX servers faster, more powerful, and more stable than NT, but the support is better, too. And consider yourself lucky if it actually solves your problem. I find it hard to believe that this story appeared on your front page. It's embarrassing. I sure didn't THINK you were into spreading Microsoft FUD. Excerpts from a letter by Torsten Holvak entitled, I'd fire someone for using NT, Source: Jesse Berst's Anchor Desk. February 16, 1998, ZDNet. The Linux machine was able to do the entire backup in 45 minutes, cutting a little over an hour off our closing time. This increase in speed came from a decrease in hardware because the Linux server was running only 32 MB in RAM and IDE hard drives where the Netware server had 64 MB in RAM and SCSI drives. The speed increase has been noticed in daily work also. I get almost daily remarks that the system seems to be running faster and more reliable. This single computer running RedHat Linux will replace both our Novell Netware 3.11 server and our Windows NT 4.0 server, while decreasing total hardware requirements. With the recent advances from the Samba team in supporting the NT domain structure and the December 1997 release of RedHat 5.0, I expect to have a very efficient and inexpensive server for our Windows 95, Windows NT and Macintosh clients. Quoted from: Replacing Windows NT Server with Linux. NT is just impossible to consider when reliability and speed are required. You could perform the same task I mentioned above on a 386 with 16 megs of RAM running FreeBSD, without paying the high Microsoft price tag. What boggles my mind is why people are investing so much in NT solutions when there is so much evidence that the UNIX solutions are more mature, stable, less expensive, and perform so much better? What is wrong with people? Do people simply not know about the capabilities of UNIX? Do people think that UNIX systems are too difficult to use? I may be biased, but when I look at desktop environments such as CDE on a Sun, or KDE, I think that's pretty close to what you find on a PC or Mac. And not only that, but you can get free versions of UNIX that are comparable in stability and scalability to Solaris, and will run quite well on PC class hardware if you so choose. And to top it all off, you can get source code. Why, in god's name, do people persist in trying to use NT? The life-blood of the Internet is the Web. This is the face that the public sees. If your site is slow, plagued with technical problems, or inaccessible, this will surely have adverse effects. Since most large corporations are UNIX-oriented, they normally go with Web server software like Apache or Netscape-Enterprise. Apache was conceived with UNIX in mind. It is free and currently rules the Internet. Roughly half the Web servers on the Internet are running Apache (see the Netcraft Web Server Survey). Microsoft's IIS Web server software does not even amount to one-quarter of all Internet-connected Web servers. Apache is currently being used by Javasoft, The FBI, Financial Times, The Movies Database, W3 Consortium, The Royal Family, Oxford University Libraries Automation Service, M.I.T., Harvard University, and the University of Texas at Austin. Netcraft also mentions that Virtual hosting company Rapidsite is now the fifth placed server in the survey. Their hosting system, running a personalised version of Apache, supports 44,280 domain names on 39,905 distinct ip addresses. An achievement, and probably the world's largest hosting system. You will recall that in the performance section of this article the UNIX-Apache marriage put the NT-IIS one to shame. Not only is Apache fast, it's freeware. For the most robust Web server a corporation could ever need, Netscape-Enterprise is a great choice. Although it is not freeware like Apache, it will meet the most demanding needs. Netscape-Enterprise is used by such companies as BMW, Dilbert, Silicon Graphics, Shell, Sun Microsystems, Sybase, Ferrari and The Vatican. Microsoft's IIS is one of the few things that actually comes with Windows NT. It does not possess any special or unique qualities not already found in other Web server software. It excels neither in speed, nor in popularity, nor in the number of concurrent hits it can handle. It is currently being used by Compaq, Nasdaq, The National Football League, Exxon, and Tesco. Given the fact that Microsoft owes much of its success to lower priced PC hardware, i.e. Intel-based machines, you would think that this great Microsoft partner would be running IIS. Well, guess again! For Windows 95 and NT users, one of the most popular places on the Web to get freeware and shareware is a site called www.windows95.com. Due to the immense popularity of the site it requires a robust operating system and performance oriented Web server software. Since all the software offered at this site is exclusively for Windows 95 or NT, and the overall flavor tends to be very pro-Microsoft, one would assume that NT servers running IIS would be the logical choice for their Internet solution. Well, here's a quote from one of their own Web pages. What hardware and software is Windows95.com running on? We use Pentium Pro computers running the BSDI UNIX operating system with Apache Web server software. Our servers are connected to the Internet via multi-homed T3 connections. Note: This quote is from February 1998. They recently changed their name from Windows95.com to WinFiles.com although they still have use of the windows95.com domain name. This change took place in March 1998. Exchange also has the advantage of requiring you to pay Microsoft for every connection to the server (per-seat charge). You can put together a system using, say a Sun Ultra 1 ($7K ballpark) and the Solaris Internet Mail Server (comes bundled with Solaris 2.6) and serve POP and IMAP to a couple of thousand people. I have yet to have anyone who wasn't a complete marketing droid tell me you can do that for twice the price with NT servers and Exchange. As I stated before, even putting aside the up-front costs, Exchange will cost you much more to operate. You have to have someone actively manage the application. If you are using UNIX mail, as long as your sendmail.cf is set up correctly, the only thing you normally have to do to administer mail is add and remove aliases. This is a huge difference in on-going costs. The trend seems to be to have the dummies be the administrators. The inference seems to be that having a GUI means that anyone can simply point and click and set it up right. The same logic would probably be applied to sendmail vs MS Exchange. Since sendmail has a text configuration file, it needs an experienced administrator. Exchange has a GUI so it does not. Having worked with both, I do not believe this. Both set up quite easily in their default configuration. Both require administrative experience to do more sophisticated things. When you get to really complicated things like SPAM filtering, you cannot get there with Exchange. Comparing Exchange, and other arbitrary MTA's is at best misrepresentation. The answer is: for UNIX, Sendmail; for Windows NT Server 4.0, nothing; however, if you want a Microsoft solution, you're pretty much stuck with Exchange Server. Washington Post Staff Writer, Elizabeth Corcoran, provides us with a real-world example: Cincinnati Bell Information Systems, for instance, has used Sun workstations and servers to process checks for several years. It recently bought several top-of-the-line Sun servers to handle the demands of a million bills a day. The choices, said James Holtman, CBIS vice president, were either Sun servers or IBM mainframes. Microsoft's technology isn't quite there yet. It has a ways to grow to match those-size systems, he said. The AberdeenGroup has published an excellent case study on migrating to Windows NT. Perhaps this is why it requires no prior purchase approval within federal agencies; NT has become the 'unofficial' standard operating system for the federal government. Why invest in an operating system that will require expensive training and re-training with each new NT release? As to the actual overall features and performance of the two operating systems, it seems that UNIX wins hands down. A UNIX operating system will give you choices: any type of hardware, CLI or GUI, commercial or GNU, diverse choice of vendors. It is dynamic, i.e. you can build a customized kernel to fit the specific computing needs at hand. NT Server is static, i.e. you will never be able to build a customized kernel. One size fits few. Although Microsoft is not the only restrictions-oriented software vendor promoting its own closed, proprietary solutions, one would hope that organizations promoting open systems and solutions would prevail. Netscape is one vendor that promotes diversity and points out Microsoft's pro-restriction, anti-choice stance regarding various products. It is the management of your company who should be reading this. The corporate IT managers notice someday what is that box in the corner and they tell them that it's the departmental Web server that's been running for a year and a half, and by the way it's running Linux. One normal reaction is to upgrade it immediately to NT, but what happens is that they go back to Linux because the performance dropped. Linus Torvalds talks economics and operating systems, InfoWorld, April 9, 1998. This very type of incident happened at Cisco Systems Inc. Obviously, some of the technical staff refused to comply with this order. Why do you think that technical people risk losing their positions over this issue? I'll leave this question for you to answer. If you are a manager, try to use this information wisely to enhance the computing environment at your facility. Talk to your technical people and ask them what works. Make the right decision. Don't be fooled by salespeople who use buzz words but can't explain them, let alone explain their pertinence to your company's computing goals. Seek out companies who have implemented both Microsoft and UNIX servers for the type of solution you are considering. Try meeting with their technical people to get objective, first-hand reports on the feasibility, difficulty of implementation, and initial+ongoing maintenance costs associated with your proposed solution. What are Major Companies Deploying? Amazon.com Books, the world's largest on-line bookstore, relies on DIGITAL UNIX AlphaServer 2000 systems to keep its Internet business open around the clock. DIGITAL VLM64 technology keeps data highly available to customers. Read what Linus Torvalds has to say about Boeing! Choosing Sun was a higher risk than other choices, but they really impressed us with their technology and commitment. Now that we've worked with Sun, if we had to do it over again, we wouldn't even consider making a different decision. Sun is doing an outstanding job. Mark Smith, Manager of Information Technology Systems, Dow Corning. This free Web-based e-mail service runs a mixture of Sun Solaris and FreeBSD. Apache 1.2.1 is the Web server software. The United States Postal Service deployed over 900 Linux based systems throughout the United States in 1997 to automatically recognize the destination addresses on mail pieces. Each system consists of 5 dual Pentium Pro 200MHz (PP200) computers and one single PP200 all running Linux. John Taves, Linux is reading your mail, April 8, 1998. This list of businesses using Linux in their day-to-day operations seeks to inform the public about the reality of Linux as a viable alternative to commerical UNIX operating systems. Companies such as Cisco Systems Inc., Sony WorldWide Networks, Mercedes-Benz, and Yellow Cab Service Corporation are mentioned. A description of the capacity in which Linux is being deployed accompanies each company's listing. Linus Torvalds, the founder of Linux, mentions in an interview with InfoWorld that Linux can often be on the unofficially approved list at some companies. But not many people want to come out of the closet to officially say they are using Linux. NASA is very open about supporting Linux, as are universities. I know that Linux is used in places like Boeing, but I can't point people to a Web page that says so. Linus Torvalds talks economics and operating systems, InfoWorld, April 9, 1998. Why did you write this article? What is your vested interest? As a person who works in this industry, it has come to my attention that an ever-increasing number of NT-only solutions are being implemented in situations for which they are ill suited. Actually, the phrase NT solutions is a bad choice of words since this implies that they work to the satisfaction of the customer. I have written this article as a public service to the enterprise, corporate, and small business entities of the world. Monopolies do not serve the customer. Microsoft is slowly becoming a monopoly, not a standard, as they would have you believe. Java is a standard. There is nothing open about Microsoft APIs. They are, and will remain, proprietary. Java applications run everywhere, Microsoft applications don't. My income is derived from supporting commercial software that runs on all platforms. If all UNIX operating systems were to dry up and disappear tomorrow, I would not be affected in any manner. As you can see, I have no vested interest in any one single operating system. I have not received, do not receive, and probably never will receive any financial support from any source whatsoever in promoting this article. Thinking in C++ 2nd edition VERSION TICA8 Revision history: TICA8, September 26, 1998. Completed the STL containers chapter. TICA7, August 14, 1998. Strings chapter modified. Other odds and ends. TICA6, August 6, 1998. Strings chapter added, still needs some work but it's in fairly good shape. The basic structure for the STL Algorithms chapter is in place and just needs to be filled out. Reorganized the chapters; this should be very close to the final organization (unless I discover I've left something out). TICA5, August 2, 1998: Lots of work done on this version. Everything compiles (except for the design patterns chapter with the Java code) under Borland C++ 5.3. This is the only compiler that even comes close, but I have high hopes for the next verison of egcs. The chapters and organization of the book is starting to take on more form. A lot of work and new material added in the STL Containers chapter (in preparation for my STL talks at the Borland and SD conferences), although that is far from finished. Also, replaced many of the situations in the first edition where I used my home-grown containers with STL containers (typically vector). Adjustment of namespace issues (using namespace std in.cpp files, full qualification of names in header files). Added appendix A to describe coding style (including namespaces). Added require.h error testing code and used it universally. Rearranged header include order to go from more general to more specific (consistency and style issue described in appendix A). On the web site, I added the broken-up versions of the files for easier downloads. TICA4, July 22, 1998: More changes and additions to the CGI Programming section at the end of Chapter 23. I think that section is finished now, with the exception of corrections. TICA3, July 14, 1998: First revision with content editing (instead of just being a posting to test the formatting and code extraction process). Changes in the end of Chapter 23, on the CGI Programming section. Minor tweaks elsewhere. RTF format should be fixed now. TICA2, July 9, 1998: Changed all fonts to Times and Courier (which are universal); changed distribution format to RTF (readable by most PC and Mac Word Processors, and by at least one on Linux: StarOffice from www.caldera.com. Please let me know if you know about other RTF word processors under Linux). The contents of the book, including the contents of the source-code files generated during automatic code extraction, are not intended to indicate any accurate or finished form of the book or source code. Please see the Web page for information about word processors that support RTF. The only fonts used are Times and Courier (so there should be no font difficulties); if you find any other fonts please report the location. Thanks for your participation in this project. Bruce Eckel This book is a tremendous achievement. You owe it to yourself to have a copy on your shelf. That the book is also an excellent tutorial on the ins and outs of C++ is an added bonus. Andrew Binstock Editor, Unix Review Bruce continues to amaze me with his insight into C++, and Thinking in C++ is his best collection of ideas yet. The entire effort is woven in a fabric that includes Eckel's own philosophy of object and program design. Published by Prentice Hall Inc. A Paramount Communications Company Englewood Cliffs, New Jersey 07632 The information in this book is distributed on an as is basis, without warranty. All rights reserved. Any of the names used in the examples and text of this book are fictional; any relationship to persons living or dead or to fictional characters in other works is purely coincidental. Printed in the United States of America 10 9 8 7 6 5 4 3 2 1 ISBN 0-13-917709-4 Prentice-Hall International (UK) Limited, London Prentice-Hall of Australia Pty. Limited, Sydney Prentice-Hall Canada, Inc., Toronto Prentice-Hall Hisapnoamericana, S.A., Mexico Prentice-Hall of India Private Limited, New Delhi Prentice-Hall of Japan, Inc., Tokyo Simon and Schuster Asia Pte. 63 Phase 2: How will we build it? 64 Phase 3: Let's build it! 160 The basic object 161 What's an object? 421 Upcasting and the copy-constructor (not indexed) 421 Composition vs. If successful, this medium of expression will be significantly easier and more flexible than the alternatives as problems grow larger and more complex. You can't just look at C++ as a collection of features; some of the features make no sense in isolation. You can only use the sum of the parts if you are thinking about design, not simply coding. And to understand C++ in this way, you must understand the problems with C and with programming in general. This book discusses programming problems, why they are problems, and the approach C++ has taken to solve such problems. Thus, the set of features I explain in each chapter will be based on the way I see a particular type of problem being solved with the language. In this way I hope to move you, a little at a time, from understanding C to the point where the C++ mindset becomes your native tongue. My primary focus was on simplifying what I found difficult - the C++ language. In this edition I have added a chapter that is a very rapid introduction to C, assuming that you have some kind of programming experience already. In addition, just as you learn many new words intuitively by seeing them in context in a novel, it's possible to learn a great deal about C from the context in which it is used in the rest of the book. Worse, my background and experience was in hardware-level embedded programming, where C has often been considered a high-level language and an inefficient overkill for pushing bits around. When I began my struggle to understand C++, the only decent book was Stroustrup's self-professed expert's guide,1 so I was left to simplify the basic concepts on my own. This resulted in my first C++ book,2 which was essentially a brain dump of my experience. That was designed as a reader's guide, to bring programmers into C and C++ at the same time. Both editions3 of the book garnered an enthusiastic response and I still feel it is a valuable resource. At about the same time that Using C++ came out, I began teaching the language. Teaching C++ has become my profession; I've seen nodding heads, blank faces, and puzzled expressions in audiences all over the world since 1989. As I began giving in-house training with smaller groups of people, I discovered something during the exercises. Even those people who were smiling and nodding were confused about many issues. I found out, by chairing the C++ track at the Software Development Conference for the last three years, that I and other speakers tended to give the typical audience too many topics, too fast. So eventually, through both variety in the audience level and the way that I presented the material, I would end up losing some portion of the audience. Maybe it's asking too much, but because I am one of those people resistant to traditional lecturing (and for most people, I believe, such resistance results from boredom), I wanted to try to keep everyone up to speed. For a time, I was creating a number of different presentations in fairly short order. Thus, I ended up learning by experiment and iteration (a technique that also works well in C++ program design). Eventually I developed a course using everything I had learned from my teaching experience, one I would be happy giving for a long time. It tackles the learning problem in discrete, easy-to-digest steps and for a hands-on seminar (the ideal learning situation), there are exercises following each of the short lessons. This book developed over the course of two years, and the material in this book has been road-tested in many forms in many different seminars. The feedback that I've gotten from each seminar has helped me change and refocus the material until I feel it works well as a teaching medium. But it isn't just a seminar handout - I tried to pack as much information as I could within these pages, and structure it to draw you through, onto the next subject. More than anything, the book is designed to serve the solitary reader, struggling with a new programming language. Goals My goals in this book are to: 1. Present the material a simple step at a time, so the reader can easily digest each concept before moving on. 2. Use examples that are as simple and short as possible. This sometimes prevents me from tackling real-world problems, but I've found that beginners are usually happier when they can understand every detail of an example rather than being impressed by the scope of the problem it solves. Also, there's a severe limit to the amount of code that can be absorbed in a classroom situation. For this I will no doubt receive criticism for using toy examples, but I'm willing to accept that in favor of producing something pedagogically useful. Those who want more complex examples can refer to the later chapters of C++ Inside and Out.4 3. Carefully sequence the presentation of features so that you aren't seeing something you haven't been exposed to. Of course, this isn't always possible; in those situations, a brief introductory description will be given. 4. Give you what I think is important for you to understand about the language, rather than everything I know. To take an example from C, if you memorize the operator precedence table (I never did) you can write clever code. So forget about precedence, and use parentheses when things aren't clear. This same attitude will be taken with some information in the C++ language, which I think is more important for compiler writers than for programmers. 5. Keep each section focused enough so the lecture time - and the time between exercise periods - is small. Not only does this keep the audience' minds more active and involved during a hands-on seminar, but it gives the reader a greater sense of accomplishment. 6. Provide the reader with a solid foundation so they can understand the issues well enough to move on to more difficult coursework and books. 7. I've endeavored not to use any particular vendor's version of C++ because, for learning the language, I don't feel like the details of a particular implementation are as important as the language itself. Most vendors' documentation concerning their own implementation specifics is adequate. Chapters C++ is a language where new and different features are built on top of an existing syntax. This course was designed with one thing in mind: the way people learn the C++ language. Audience feedback helped me understand which parts were difficult and needed extra illumination. As a result, I've taken a great deal of trouble to introduce the features as few at a time as possible; ideally, only one at a time per chapter. The goal, then, is for each chapter to teach a single feature, or a small group of associated features, in such a way that no additional features are relied upon. That way you can digest each piece in the context of your current knowledge before moving on. To accomplish this, I leave many C features in place much longer than I would prefer. This is also true with many other features in the language. Here is a brief description of the chapters contained in this book. You'll also learn about the benefits and concerns of adopting the language and suggestions for moving into the world of C++. (1) Data abstraction. Most features in C++ revolve around this key concept: the ability to create new data types. Not only does this provide superior code organization, but it lays the ground for more powerful OOP abilities. You'll see how this idea is facilitated by the simple act of putting functions inside structures, the details of how to do it, and what kind of code it creates. You can decide that some of the data and functions in your structure are unavailable to the user of the new type by making them private. This means you can separate the underlying implementation from the interface that the client programmer sees, and thus allow that implementation to be easily changed without affecting client code. The keyword class is also introduced as a fancier way to describe a new data type, and the meaning of the word object is demystified (it's a variable on steroids). One of the most common C errors results from uninitialized variables. The constructor in C++ allows you to guarantee that variables of your new data type (objects of your class) will always be properly initialized. If your objects also require some sort of cleanup, you can guarantee that this cleanup will always happen with the C++ destructor. C++ is intended to help you build big, complex projects. While doing this, you may bring in multiple libraries that use the same function name, and you may also choose to use the same name with different meanings within a single library. C++ makes this easy with function overloading, which allows you to reuse the same function name as long as the argument lists are different. Default arguments allow you to call the same function in different ways by automatically providing default values for some of your arguments. This chapter covers the const and volatile keywords that have additional meaning in C++, especially inside classes. It also shows how the meaning of const varies inside and outside classes and how to create compile-time constants in classes. Preprocessor macros eliminate function call overhead, but the preprocessor also eliminates valuable C++ type checking. The inline function gives you all the benefits of a preprocessor macro plus all the benefits of a real function call. Creating names is a fundamental activity in programming, and when a project gets large, the number of names can be overwhelming. C++ allows you a great deal of control over names: creation, visibility, placement of storage, and linkage. This chapter shows how names are controlled using two techniques. First, the static keyword is used to control visibility and linkage, and its special meaning with classes is explored. A far more useful technique for controlling names at the global scope is C++'s namespace feature, which allows you to break up the global name space into distinct regions. C++ pointers work like C pointers with the additional benefit of stronger C++ type checking. There's a new way to handle addresses; from Algol and Pascal, C++ lifts the reference which lets the compiler handle the address manipulation while you use ordinary notation. You'll also meet the copy-constructor, which controls the way objects are passed into and out of functions by value. Finally, the C++ pointer-to-member is illuminated. This feature is sometimes called syntactic sugar. It lets you sweeten the syntax for using your type by allowing operators as well as function calls. In this chapter you'll learn that operator overloading is just a different type of function call and how to write your own, especially the sometimes-confusing uses of arguments, return types, and making an operator a member or friend. How many planes will an air-traffic system have to handle? How many shapes will a CAD system need? In the general programming problem, you can't know the quantity, lifetime or type of the objects needed by your running program. In this chapter, you'll learn how C++'s new and delete elegantly solve this problem by safely creating objects on the heap. Data abstraction allows you to create new types from scratch; with composition and inheritance, you can create new types from existing types. With composition you assemble a new type using other types as pieces, and with inheritance you create a more specific version of an existing type. In this chapter you'll learn the syntax, how to redefine functions, and the importance of construction and destruction for inheritance and composition. On your own, you might take nine months to discover and understand this cornerstone of OOP. Through small, simple examples you'll see how to create a family of types with inheritance and manipulate objects in that family through their common base class. The virtual keyword allows you to treat all objects in this family generically, which means the bulk of your code doesn't rely on specific type information. This makes your programs extensible, so building programs and code maintenance is easier and cheaper. Inheritance and composition allow you to reuse object code, but that doesn't solve all your reuse needs. Templates allow you to reuse source code by providing the compiler with a way to substitute type names in the body of a class or function. This supports the use of container class libraries, which are important tools for the rapid, robust development of object-oriented programs. This extensive chapter gives you a thorough grounding in this essential subject. This sounds simple at first: A new class is inherited from more than one existing class. However, you can end up with ambiguities and multiple copies of base-class objects. That problem is solved with virtual base classes, but the bigger issue remains: When do you use it? Multiple inheritance is only essential when you need to manipulate an object through more than one common base class. This chapter explains the syntax for multiple inheritance, and shows alternative approaches - in particular, how templates solve one common problem. The use of multiple inheritance to repair a damaged class interface is demonstrated as a genuinely valuable use of this feature. Error handling has always been a problem in programming. Even if you dutifully return error information or set a flag, the function caller may simply ignore it. Exception handling is a primary feature in C++ that solves this problem by allowing you to throw an object out of your function when a critical error happens. You throw different types of objects for different errors, and the function caller catches these objects in separate error handling routines. If you throw an exception, it cannot be ignored, so you can guarantee that something will happen in response to your error. Run-time type identification (RTTI) lets you find the exact type of an object when you only have a pointer or reference to the base type. Normally, you'll want to intentionally ignore the exact type of an object and let the virtual function mechanism implement the correct behavior for that type. But occasionally it is very helpful to know the exact type of an object for which you only have a base pointer; often this information allows you to perform a special-case operation more efficiently. This chapter explains what RTTI is for and how to use it. Appendix A: Etcetera. At this writing, the C++ Standard is unfinished. Although virtually all the features that will end up in the language have been added to the standard, some haven't appeared in all compilers. This appendix briefly mentions some of the other features you should look for in your compiler (or in future releases of your compiler). Appendix B: Programming guidelines. This appendix is a series of suggestions for C++ programming. They've been collected over the course of my teaching and programming experience, and also from the insights of other teachers. Many of these tips are summarized from the pages of this book. Appendix C: Simulating virtual constructors. The constructor cannot have any virtual qualities, and this sometimes produces awkward code. These are fairly simple, so they can be finished in a reasonable amount of time in a classroom situation while the instructor observes, making sure all the students are absorbing the material. Some exercises are a bit more challenging to keep advanced students entertained. They're all designed to be solved in a short time and are only there to test and polish your knowledge rather than present major challenges (presumably, you'll find those on your own - or more likely they'll find you). The copyright prevents you from republishing the code in print media without permission. To unpack the code, you download the text version of the book and run the program ExtractCode (from chapter 23), the source for which is also provided on the Web site. The program will create a directory for each chapter and unpack the code into those directories. Permission is granted to use this file in classroom situations, including its use in presentation materials, as long as the book Thinking in C++ is cited as the source. You cannot remove this copyright and notice. You cannot distribute modified versions of the source code in this package. You cannot use this file in printed media without the express permission of the author. Bruce Eckel makes no representation about the suitability of this software for any purpose. It is provided as is without express or implied warranty of any kind, including any implied warranty of merchantability, fitness for a particular purpose or non-infringement. The entire risk as to the quality and performance of the software is with you. Bruce Eckel and the publisher shall not be liable for any damages suffered by you or any third party as a result of using or distributing software. Should the software prove defective, you assume the cost of all necessary servicing, repair, or correction. If you think you've found an error, please submit the correction using the form you will find at www.BruceEckel.com. Coding standards In the text of this book, identifiers (function, variable, and class names) will be set in bold. Most keywords will also be set in bold, except for those keywords which are used so much that the bolding can become tedious, like class and virtual. I use a particular coding style for the examples in this book. Because C++ is a free-form programming language, you can continue to use whatever style you're comfortable with. The programs in this book are files that are automatically extracted from the text of the book, which allows them to be tested to ensure they work correctly. Errors discovered and reported to the author will appear first in the electronic version of the book (at www.BruceEckel.com) and later in updates of the book. One of the standards in this book is that all programs will compile and link without errors (although they will sometimes cause warnings). The standard for main( ) is to return an int, but Standard C++ states that if there is no return statement inside main( ), the compiler will automatically generate code to return 0. This option will be used in this book (although some compilers may still generate warnings for this). Thus, I will use the term Standard C++. Language support Your compiler may not support all the features discussed in this book, especially if you don't have the newest version of your compiler. Implementing a language like C++ is a Herculean task, and you can expect that the features will appear in pieces rather than all at once. But if you attempt one of the examples in the book and get a lot of errors from the compiler, it's not necessarily a bug in the code or the compiler - it may simply not be implemented in your particular compiler yet. Seminars and CD Roms My company provides public hands-on training seminars based on the material in this book. Selected material from each chapter represents a lesson, which is followed by a monitored exercise period so each student receives personal attention. If you have specific questions, you may direct them to Bruce@EckelObjects.com. Errors No matter how many tricks a writer uses to detect errors, some always creep in and these often leap off the page for a fresh reader. Your help is appreciated. I have been presenting this material on tours produced by Miller Freeman Inc. Richard's insights and support have been very helpful (and Kim's, too). Thanks also to KoAnn Vikoren, Eric Faurot, Jennifer Jessup, Nicole Freeman, Barbara Hanscome, Regina Ridley, Alex Dunne, and the rest of the cast and crew at MFI. However, I produced the camera-ready pages myself, so the typesetting errors are mine. Microsoft(r) Word for Windows 97 was used to write the book and to create camera-ready pages. Thanks to Alan Apt, Sondra Chavez, Mona Pompili, Shirley McGuire, and everyone else there who made life easy for me. A special thanks to all my teachers, and all my students (who are my teachers as well). Personal thanks to my friends Gen Kiyooka and Kraig Brockschmidt. And of course, Mom and Dad. 1: Introduction to objects The genesis of the computer revolution was in a machine. The genesis of our programming languages thus tends to look like that machine. But the computer is not so much a machine as it is a mind amplification tool and a different kind of expressive medium. As a result, the tools are beginning to look less like machines and more like parts of our minds, and more like other expressive mediums like writing, painting, sculpture, animation or filmmaking. Object-oriented programming is part of this movement toward the computer as an expressive medium. This chapter will introduce you to the basic concepts of object-oriented programming (OOP), followed by a discussion of OOP development methods. Finally, strategies for moving yourself, your projects, and your company to object-oriented programming are presented. This chapter is background and supplementary material. If you're eager to get to the specifics of the language, feel free to jump ahead to later chapters. You can always come back here and fill in your knowledge later. The progress of abstraction All programming languages provide abstractions. It can be argued that the complexity of the problems you can solve is directly related to the kind and quality of abstraction. By kind I mean: what is it that you are abstracting? Assembly language is a small abstraction of the underlying machine. Many so-called imperative languages that followed (such as FORTRAN, BASIC, and C) were abstractions of assembly language. These languages are big improvements over assembly language, but their primary abstraction still requires you to think in terms of the structure of the computer rather than the structure of the problem you are trying to solve. The programmer must establish the association between the machine model (in the solution space) and the model of the problem that is actually being solved (in the problem space). The alternative to modeling the machine is to model the problem you're trying to solve. Early languages such as LISP and APL chose particular views of the world (all problems are ultimately lists or all problems are algorithmic). PROLOG casts all problems into chains of decisions. Languages have been created for constraint-based programming and for programming exclusively by manipulating graphical symbols. The object-oriented approach takes a step farther by providing tools for the programmer to represent elements in the problem space. This representation is general enough that the programmer is not constrained to any particular type of problem. This is a more flexible and powerful language abstraction than what we've had before. Thus OOP allows you to describe the problem in terms of the problem, rather than in the terms of the solution. There's still a connection back to the computer, though. Each object looks quite a bit like a little computer; it has a state, and it has operations you can ask it to perform. However, this doesn't seem like such a bad analogy to objects in the real world; they all have characteristics and behaviors. Alan Kay summarized five basic characteristics of Smalltalk, the first successful object-oriented language and one of the languages upon which C++ is based. These characteristics represent a pure approach to object-oriented programming: 1. Everything is an object. Think of an object as a fancy variable; it stores data, but you can also ask it to perform operations on itself by making requests. In theory, you can take any conceptual component in the problem you're trying to solve (dogs, buildings, services, etc.) and represent it as an object in your program. 2. A program is a bunch of objects telling each other what to do by sending messages. To make a request of an object, you send a message to that object. More concretely, you can think of a message as a request to call a function that belongs to a particular object. 3. Each object has its own memory made up of other objects. Or, you make a new kind of object by making a package containing existing objects. Thus, you can build up complexity in a program while hiding it behind the simplicity of objects. 4. Every object has a type. This is actually a very loaded statement, as you will see later. Because an object of type circle is also an object of type shape, a circle is guaranteed to receive shape messages. This means you can write code that talks to shapes and automatically handle anything that fits the description of a shape. This substitutability is one of the most powerful concepts in OOP. Simula, as its name implies, was created for developing simulations such as the classic bank teller problem. In this, you have a bunch of tellers, customers, accounts, transactions, etc. The members (elements) of each class share some commonality: every account has a balance, every teller can accept a deposit, etc. At the same time, each member has its own state; each account has a different balance, each teller has a name. Thus the tellers, customers, accounts, transactions, etc. This entity is the object, and each object belongs to a particular class that defines its characteristics and behaviors. So, although what we really do in object-oriented programming is create new data types, virtually all object-oriented programming languages use the class keyword. When you see the word type think class and vice versa. Once a type is established, you can make as many objects of that type as you like, and then manipulate those objects as the elements that exist in the problem you are trying to solve. But how do you get an object to do useful work for you? There must be a way to make a request of that object so it will do something, such as complete a transaction, draw something on the screen or turn on a switch. And each object can satisfy only certain requests. The requests you can make of an object are defined by its interface, and the type is what determines the interface. The idea of type being equivalent to interface is fundamental in object-oriented programming. You create a Light object simply by declaring a name (lt) for that identifier. To send a message to the object, you state the name and connect it to the message name with a period (dot). From the standpoint of the user of a pre-defined class, that's pretty much all there is to programming with objects. The hidden implementation It is helpful to break up the playing field into class creators (those who create new data types) and client programmers8 (the class consumers who use the data types in their applications). The goal of the client programmer is to collect a toolbox full of classes to use for rapid application development. The goal of the class creator is to build a class that exposes only what's necessary to the client programmer and keeps everything else hidden. If it's hidden, the client programmer can't use it, which means that the class creator can change the hidden portion at will without worrying about the impact to anyone else. The interface establishes what requests you can make for a particular object. However, there must be code somewhere to satisfy that request. This, along with the hidden data, comprises the implementation. From a procedural programming standpoint, it's not that complicated. A type has a function associated with each possible request, and when you make a particular request to an object, that function is called. This process is often summarized by saying that you send a message (make a request) to an object, and the object figures out what to do with that message (it executes code). In any relationship it's important to have boundaries that are respected by all parties involved. When you create a library, you establish a relationship with the client programmer, who is another programmer, but one who is putting together an application or using your library to build a bigger library. If all the members of a class are available to everyone, then the client programmer can do anything with that class and there's no way to force any particular behaviors. Even though you might really prefer that the client programmer not directly manipulate some of the members of your class, without access control there's no way to prevent it. Everything's naked to the world. There are two reasons for controlling access to members. The first is to keep client programmers' hands off portions they shouldn't touch - parts that are necessary for the internal machinations of the data type but not part of the interface that users need to solve their particular problems. This is actually a service to users because they can easily see what's important to them and what they can ignore. The second reason for access control is to allow the library designer to change the internal workings of the structure without worrying about how it will affect the client programmer. For example, you might implement a particular class in a simple fashion to ease development, and then later decide you need to rewrite it to make it run faster. If the interface and implementation are clearly separated and protected, you can accomplish this and require only a relink by the user. C++ uses three explicit keywords and one implied keyword to set the boundaries in a class: public, private, protected and the implied friendly, which is what you get if you don't specify one of the other keywords. Their use and meaning are remarkably straightforward. These access specifiers determine who can use the definition that follows. The private keyword, on the other hand, means that no one can access that definition except you, the creator of the type, inside function members of that type. If someone tries to access a private member, they'll get a compile-time error. Friendly has to do with something called a package, which is C++'s way of making libraries. If something is friendly it's available only within the package. Inheritance will be covered shortly. Reusing the implementation Once a class has been created and tested, it should (ideally) represent a useful unit of code. It turns out that this reusability is not nearly so easy to achieve as many would hope; it takes experience and insight to achieve a good design. But once you have such a design, it begs to be reused. Code reuse is arguably the greatest leverage that object-oriented programming languages provide. The simplest way to reuse a class is to just use an object of that class directly, but you can also place an object of that class inside a new class. We call this creating a member object. Your new class can be made up of any number and type of other objects, whatever is necessary to achieve the functionality desired in your new class. This concept is called composition, since you are composing a new class from existing classes. Sometimes composition is referred to as a has-a relationship, as in a car has a trunk. Composition comes with a great deal of flexibility. The member objects of your new class are usually private, making them inaccessible to client programmers using the class. This allows you to change those members without disturbing existing client code. You can also change the member objects at run time, which provides great flexibility. Inheritance, which is described next, does not have this flexibility since the compiler must place restrictions on classes created with inheritance. Because inheritance is so important in object-oriented programming it is often highly emphasized, and the new programmer can get the idea that inheritance should be used everywhere. This can result in awkward and overcomplicated designs. Instead, you should first look to composition when creating new classes, since it is simpler and more flexible. If you take this approach, your designs will stay cleaner. It will be reasonably obvious when you need inheritance. Inheritance: reusing the interface By itself, the concept of an object is a convenient tool. It allows you to package data and functionality together by concept, so you can represent an appropriate problem-space idea rather than being forced to use the idioms of the underlying machine. These concepts are expressed in the primary idea of the programming language as a data type (using the class keyword). It seems a pity, however, to go to all the trouble to create a data type and then be forced to create a brand new one that might have similar functionality. It's nicer if we can take the existing data type, clone it and make additions and modifications to the clone. Inheritance is implemented in C++ using a special syntax that names another class as what is commonly referred to as the base class. That is, all the messages you can send to objects of the base class you can also send to objects of the derived class. Since we know the type of a class by the messages we can send to it, this means that the derived class is the same type as the base class. This type equivalence via inheritance is one of the fundamental gateways in understanding the meaning of object-oriented programming. Since both the base class and derived class have the same interface, there must be some implementation to go along with that interface. That is, there must be some code to execute when an object receives a particular message. If you simply inherit a class and don't do anything else, the methods from the base-class interface come right along into the derived class. That means objects of the derived class have not only the same type, they also have the same behavior, which doesn't seem particularly interesting. You have two ways to differentiate your new derived class from the original base class it inherits from. The first is quite straightforward: you simply add brand new functions to the derived class. These new functions are not part of the base class interface. This means that the base class simply didn't do as much as you wanted it to, so you add more functions. This simple and primitive use for inheritance is, at times, the perfect solution to your problem. However, you should look closely for the possibility that your base class might need these additional functions. Overriding base-class functionality Although inheritance may sometimes imply that you are going to add new functions to the interface, that's not necessarily true. The second way to differentiate your new class is to change the behavior of an existing base-class function. This is referred to as overriding that function. To override a function, you simply create a new definition for the function in the derived class. You're saying I'm using the same interface function here, but I want it to do something different for my new type. Is-a vs. This means that the derived type is exactly the same type as the base class since it has exactly the same interface. As a result, you can exactly substitute an object of the derived class for an object of the base class. This can be thought of as pure substitution. In a sense, this is the ideal way to treat inheritance. There are times when you must add new interface elements to a derived type, thus extending the interface and creating a new type. The new type can still be substituted for the base type, but the substitution isn't perfect in a sense because your new functions are not accessible from the base type. This can be described as an is-like-a relationship; the new type has the interface of the old type but it also contains other functions, so you can't really say it's exactly the same. For example, consider an air conditioner. Suppose your house is wired with all the controls for cooling; that is, it has an interface that allows you to control cooling. Imagine that the air conditioner breaks down and you replace it with a heat pump, which can both heat and cool. The heat pump is-like-an air conditioner, but it can do more. Because your house is wired only to control cooling, it is restricted to communication with the cooling part of the new object. The interface of the new object has been extended, and the existing system doesn't know about anything except the original interface. When you see the substitution principle it's easy to feel like that's the only way to do things, and in fact it is nice if your design works out that way. But you'll find that there are times when it's equally clear that you must add new functions to the interface of a derived class. With inspection both cases should be reasonably obvious. Interchangeable objects with polymorphism Inheritance usually ends up creating a family of classes, all based on the same uniform interface. We express this with an inverted tree diagram:9 One of the most important things you do with such a family of classes is to treat an object of a derived class as an object of the base class. This is important because it means you can write a single piece of code that ignores the specific details of type and talks just to the base class. That code is then decoupled from type-specific information, and thus is simpler to write and easier to understand. And, if a new type - a Triangle, for example - is added through inheritance, the code you write will work just as well for the new type of Shape as it did on the existing types. Thus the program is extensible. Consider the above example. This is actually a pretty amazing trick. Consider the line: doStuff(c); What's happening here is that a Circle pointer is being passed into a function that's expecting a Shape pointer. Since a Circle is a Shape it can be treated as one by doStuff( ). That is, any message that doStuff( ) can send to a Shape, a Circle can accept. So it is a completely safe and logical thing to do. We call this process of treating a derived type as though it were its base type upcasting. The name cast is used in the sense of casting into a mold and the up comes from the way the inheritance diagram is typically arranged, with the base type at the top and the derived classes fanning out downward. Thus, casting to a base type is moving up the inheritance diagram: upcasting. An object-oriented program contains some upcasting somewhere, because that's how you decouple yourself from knowing about the exact type you're working with. Here, you just say You're a shape, I know you can erase( ) yourself, do it and take care of the details correctly. Dynamic binding What's amazing about the code in doStuff( ) is that somehow the right thing happens. This is amazing because when the C++ compiler is compiling the code for doStuff( ), it cannot know exactly what types it is dealing with. So ordinarily, you'd expect it to end up calling the version of erase( ) for Shape, and draw( ) for Shape and not for the specific Circle, Square, or Line. And yet the right thing happens. Here's how it works. When you send a message to an object even though you don't know what specific type it is, and the right thing happens, that's called polymorphism. The process used by object-oriented programming languages to implement polymorphism is called dynamic binding. The compiler and run-time system handle the details; all you need to know is that it happens and more importantly how to design with it. Some languages require you to use a special keyword to enable dynamic binding. In C++ this keyword is virtual. In C++, you must remember to add a keyword because by default member functions are not dynamically bound. If a member function is virtual, then when you send a message to an object, the object will do the right thing, even when upcasting is involved. Abstract base classes and interfaces Often in a design, you want the base class to present only an interface for its derived classes. That is, you don't want anyone to actually create an object of the base class, only to upcast to it so that its interface can be used. This is accomplished by making that class abstract by giving it at least one pure virtual function. You can recognize a pure virtual function because it uses the virtual keyword and is followed by = 0. If anyone tries to make an object of an abstract class, the compiler prevents them. This is a tool to enforce a particular design. When an abstract class is inherited, all pure virtual functions must be implemented, or the inherited class becomes abstract as well. Creating a pure virtual function allows you to put a member function in an interface without being forced to provide a possibly meaningless body of code for that member function. Objects: characteristics + behaviors10 The first object-oriented programming language was Simula-67, developed in the sixties to solve, as the name implies, simulation problems. A class describes a set of objects that have identical characteristics (data elements) and behaviors (functionality). So a class is really a data type because a floating point number (for example) also has a set of characteristics and behaviors. The difference is that a programmer defines a class to fit a problem rather than being forced to use an existing data type that was designed to represent a unit of storage in a machine. You extend the programming language by adding new data types specific to your needs. The programming system welcomes the new classes and gives them all the care and type-checking that it gives to built-in types. This approach was not limited to building simulations. Whether or not you agree that any program is a simulation of a system you design, the use of OOP techniques can easily reduce a large set of problems to a simple solution. This discovery spawned a number of OOP languages, most notably Smalltalk - the most successful OOP language until C++. Abstract data typing is a fundamental concept in object-oriented programming. Inheritance: type relationships A type does more than describe the constraints on a set of objects; it also has a relationship with other types. Two types can have characteristics and behaviors in common, but one type may contain more characteristics than another and may also handle more messages (or handle them differently). Inheritance expresses this similarity between types with the concept of base types and derived types. A base type contains all the characteristics and behaviors that are shared among the types derived from it. You create a base type to represent the core of your ideas about some objects in your system. From the base type, you derive other types to express the different ways that core can be realized. For example, a garbage-recycling machine sorts pieces of garbage. The base type is garbage, and each piece of garbage has a weight, a value, and so on and can be shredded, melted, or decomposed. From this, more specific types of garbage are derived that may have additional characteristics (a bottle has a color) or behaviors (an aluminum can may be crushed, a steel can is magnetic). In addition, some behaviors may be different (the value of paper depends on its type and condition). Using inheritance, you can build a type hierarchy that expresses the problem you're trying to solve in terms of its types. A second example is the classic shape problem, perhaps used in a computer-aided design system or game simulation. The base type is shape, and each shape has a size, a color, a position, and so on. Each shape can be drawn, erased, moved, colored, and so on. From this, specific types of shapes are derived (inherited): circle, square, triangle, and so on, each of which may have additional characteristics and behaviors. Certain shapes can be flipped, for example. Some behaviors may be different (calculating the area of a shape). The type hierarchy embodies both the similarities and differences between the shapes. With objects, the type hierarchy is the primary model, so you go directly from the description of the system in the real world to the description of the system in code. Indeed, one of the difficulties people have with object-oriented design is that it's too simple to get from the beginning to the end. A mind trained to look for complex solutions is often stumped by this simplicity at first. Polymorphism When dealing with type hierarchies, you often want to treat an object not as the specific type that it is but as a member of its base type. This allows you to write code that doesn't depend on specific types. In the shape example, functions manipulate generic shapes without respect to whether they're circles, squares, triangles, and so on. All shapes can be drawn, erased, and moved, so these functions simply send a message to a shape object; they don't worry about how the object copes with the message. Such code is unaffected by the addition of new types, which is the most common way to extend an object-oriented program to handle new situations. For example, you can derive a new subtype of shape called pentagon without modifying the functions that deal only with generic shapes. The ability to extend a program easily by deriving new subtypes is important because it greatly reduces the cost of software maintenance. If a function is going to tell a generic shape to draw itself, or a generic vehicle to steer, or a generic bird to fly, the compiler cannot know at compile-time precisely what piece of code will be executed. If you add a new subtype, the code it executes can be different without changes to the function call. The compiler cannot know precisely what piece of code is executed, so what does it do? The answer is the primary twist in object-oriented programming: The compiler cannot make a function call in the traditional sense. The function call generated by a non-OOP compiler causes what is called early binding, a term you may not have heard before because you've never thought about it any other way. It means the compiler generates a call to a specific function name, and the linker resolves that call to the absolute address of the code to be executed. In OOP, the program cannot determine the address of the code until run-time, so some other scheme is necessary when a message is sent to a generic object. To solve the problem, object-oriented languages use the concept of late binding. When you send a message to an object, the code being called isn't determined until run-time. The compiler does ensure that the function exists and performs type checking on the arguments and return value (a language where this isn't true is called weakly typed), but it doesn't know the exact code to execute. To perform late binding, the compiler inserts a special bit of code in lieu of the absolute call. This code calculates the address of the function body to execute at run-time using information stored in the object itself (this subject is covered in great detail in Chapter 13). Thus, each object can behave differently according to the contents of that pointer. When you send a message to an object, the object actually does figure out what to do with that message. You state that you want a function to have the flexibility of late-binding properties using the keyword virtual. You don't need to understand the mechanics of virtual to use it, but without it you can't do object-oriented programming in C++. Virtual functions allow you to express the differences in behavior of classes in the same family. Those differences are what cause polymorphic behavior. Manipulating concepts: what an OOP program looks like You know what a procedural program in C looks like: data definitions and function calls. To find the meaning of such a program you have to work a little, looking through the function calls and low-level concepts to create a model in your mind. This is the reason we need intermediate representations for procedural programs - they tend to be confusing because the terms of expression are oriented more toward the computer than the problem you're solving. Because C++ adds many new concepts to the C language, your natural assumption may be that, of course, the main( ) in a C++ program will be far more complicated than the equivalent C program. Here, you'll be pleasantly surprised: A well-written C++ program is generally far simpler and much easier to understand than the equivalent C program. What you'll see are the definitions of the objects that represent concepts in your problem space (rather than the issues of the computer representation) and messages sent to those objects to represent the activities in that space. One of the delights of object-oriented programming is that it's generally very easy to understand the code by reading it. Usually there's a lot less code, as well, because many of your problems will be solved by reusing existing library code. Object landscapes and lifetimes Technically, OOP is just about abstract data typing, inheritance and polymorphism, but other issues can be at least as important. The remainder of this section will cover these issues. One of the most important factors is the way objects are created and destroyed. Where is the data for an object and how is the lifetime of the object controlled? There are different philosophies at work here. C++ takes the approach that control of efficiency is the most important issue, so it gives the programmer a choice. For maximum run-time speed, the storage and lifetime can be determined while the program is being written, by placing the objects on the stack (these are sometimes called automatic or scoped variables) or in the static storage area. This places a priority on the speed of storage allocation and release, and control of these can be very valuable in some situations. However, you sacrifice flexibility because you must know the exact quantity, lifetime and type of objects while you're writing the program. If you are trying to solve a more general problem such as computer-aided design, warehouse management or air-traffic control, this is too restrictive. The second approach is to create objects dynamically in a pool of memory called the heap. In this approach you don't know until run time how many objects you need, what their lifetime is or what their exact type is. Those are determined at the spur of the moment while the program is running. If you need a new object, you simply make it on the heap at the point that you need it. Because the storage is managed dynamically, at run time, the amount of time required to allocate storage on the heap is significantly longer than the time to create storage on the stack. In addition, the greater flexibility is essential to solve the general programming problem. C++ allows you to determine whether the objects are created while you write the program or at run time to allow the control of efficiency. You might think that since it's more flexible, you'd always want to create objects on the heap rather than the stack. There's another issue, however, and that's the lifetime of an object. If you create an object on the stack or in static storage, the compiler determines how long the object lasts and can automatically destroy it. However, if you create it on the heap the compiler has no knowledge of its lifetime. Of course, a garbage collector is much more convenient, but it requires that all applications must be able to tolerate the existence of the garbage collector and the other overhead for garbage collection. This does not meet the design requirements of the C++ language and so it was not included, but C++ does have a garbage collector (as does Smalltalk; Delphi does not but one could be added. Third-party garbage collectors exist for C++). The rest of this section looks at additional factors concerning object lifetimes and landscapes. Containers and iterators If you don't know how many objects you're going to need to solve a particular problem, or how long they will last, you also don't know how to store those objects. How can you know how much space to create for those objects? You can't, since that information isn't known until run time. The solution to most problems in object-oriented design seems flippant: you create another type of object. The new type of object that solves this particular problem holds objects, or pointers to objects. Of course, you can do the same thing with an array, which is available in most languages. But there's more. This new type of object, which is typically referred to in C++ as a container (also called a collection in some languages), will expand itself whenever necessary to accommodate everything you place inside it. So you don't need to know how many objects you're going to hold in a collection. Just create a collection object and let it take care of the details. Fortunately, a good OOP language comes with a set of containers as part of the package. In C++, it's the Standard Template Library (STL). Object Pascal has containers in its Visual Component Library (VCL). Smalltalk has a very complete set of containers. Java has a standard set of containers. These may include sets, queues, hash tables, trees, stacks, etc. All containers have some way to put things in and get things out. The way that you place something into a container is fairly obvious. There's a function called push or add or a similar name. Fetching things out of a container is not always as apparent; if it's an array-like entity such as a vector, you might be able to use an indexing operator or function. But in many situations this doesn't make sense. Also, a single-selection function is restrictive. What if you want to manipulate or compare a set of elements in the container instead of just one? The solution is an iterator, which is an object whose job is to select the elements within a container and present them to the user of the iterator. As a class, it also provides a level of abstraction. This abstraction can be used to separate the details of the container from the code that's accessing that container. The container, via the iterator, is abstracted to be simply a sequence. The iterator allows you to traverse that sequence without worrying about the underlying structure - that is, whether it's a vector, a linked list, a stack or something else. This gives you the flexibility to easily change the underlying data structure without disturbing the code in your program. From the design standpoint, all you really want is a sequence that can be manipulated to solve your problem. If a single type of sequence satisfied all of your needs, there'd be no reason to have different kinds. There are two reasons that you need a choice of containers. First, containers provide different types of interfaces and external behavior. A stack has a different interface and behavior than that of a queue, which is different than that of a set or a list. One of these might provide a more flexible solution to your problem than the other. Second, different containers have different efficiencies for certain operations. The best example is a vector and a list. Both are simple sequences that can have identical interfaces and external behaviors. But certain operations can have radically different costs. Randomly accessing elements in a vector is a constant-time operation; it takes the same amount of time regardless of the element you select. However, in a linked list it is expensive to move through the list to randomly select an element, and it takes longer to find an element if it is further down the list. On the other hand, if you want to insert an element in the middle of a sequence, it's much cheaper in a list than in a vector. These and other operations have different efficiencies depending upon the underlying structure of the sequence. In the design phase, you might start with a list and, when tuning for performance, change to a vector. Because of the abstraction via iterators, you can change from one to the other with minimal impact on your code. In the end, remember that a container is only a storage cabinet to put objects in. If that cabinet solves all of your needs, it doesn't really matter how it is implemented (a basic concept with most types of objects). You might need only one type of sequence. You can even imagine the perfect container abstraction, which can automatically change its underlying implementation according to the way it is used. Exception handling: dealing with errors Ever since the beginning of programming languages, error handling has been one of the most difficult issues. A major problem with most error-handling schemes is that they rely on programmer vigilance in following an agreed-upon convention that is not enforced by the language. If the programmer is not vigilant, which is often if they are in a hurry, these schemes can easily be forgotten. Exception handling wires error handling directly into the programming language and sometimes even the operating system. An exception is an object that is thrown from the site of the error and can be caught by an appropriate exception handler designed to handle that particular type of error. It's as if exception handling is a different, parallel path of execution that can be taken when things go wrong. And because it uses a separate execution path, it doesn't need to interfere with your normally-executing code. This makes that code simpler to write since you aren't constantly forced to check for errors. In addition, a thrown exception is unlike an error value that's returned from a function or a flag that's set by a function in order to indicate an error condition, These can be ignored. An exception cannot be ignored so it's guaranteed to be dealt with at some point. Finally, exceptions provide a way to reliably recover from a bad situation. Instead of just exiting you are often able to set things right and restore the execution of a program, which produces much more robust programs. It's worth noting that exception handling isn't an object-oriented feature, although in object-oriented languages the exception is normally represented with an object. Exception handling existed before object-oriented languages. Introduction to methods A method is a set of processes and heuristics used to break down the complexity of a programming problem. Especially in OOP, methodology is a field of many experiments, so it is important to understand the problem the method is trying to solve before you consider adopting one. This is particularly true with C++, where the programming language itself is intended to reduce the complexity involved in expressing a program. This may in fact alleviate the need for ever-more-complex methodologies. Instead, simpler ones may suffice in C++ for a much larger class of problems than you could handle with simple methods for procedural languages. Its also important to realize that the term methodology is often too grand and promises too much. Whatever you do now when you design and write a program is a method. It may be your own method, and you may not be conscious of doing it, but it is a process you go through as you create. If it is an effective process, it may need only a small tune-up to work with C++. If you are not satisfied with your productivity and the way your programs turn out, you may want to consider adopting a formal method. Complexity To analyze this situation, I shall start with a premise: Computer programming is about managing complexity by imposing discipline. 9. External discipline is seen in the meta-information about the program, loosely described as design documentation (not to be confused with product documentation). Both creation and maintenance are fundamental properties of a program's lifetime, and a useful programming method will integrate both in the most expedient fashion, without going overboard in one direction or another. Not all these developments caught on; often the ideas originated in the academic world and spread into the computing world at large depending on the set of problems they were well suited for. The successful language in pure computer science was Lisp (List-Processing), while the more mathematically oriented could use APL (A Programming Language). All of these languages had in common their use of procedures. Lisp and APL were created with language elegance in mind - the mission statement of the language is embodied in an engine that handles all cases of that mission. FORTRAN and COBOL were created to solve specific types of problems, and then evolved when those problems got more complex or new ones appeared. Even in their twilight years they continue to evolve: Versions of both FORTRAN and COBOL are appearing with object-oriented extensions. Other languages also appeared, successfully solved a subset of the programming problem, and took their place in the order of things. Two of the most interesting of these were Prolog, built around an inference engine (something you see popping up in other languages, often as a library) and FORTH, which is an extensible language. FORTH allows the programmer to re-form the language itself until it fits the problem, a concept akin to object-oriented programming. However, FORTH also allows you to change the base language itself. Numerous other languages have been invented to solve a portion of the programming problem. Usually, these languages begin with a particular objective in mind. BASIC (Beginners All-purpose Symbolic Instruction Code), for example, was designed in the 60's to make programming simpler for the beginner. APL was designed for mathematical manipulations. Both languages can solve other problems, but the question becomes whether they are the most ideal solutions for the entire problem set. However, two factors eventually creep in: the management of complexity, and maintenance (discussed in the next section). In fact, the boundary of chaos is fuzzy rather than clear: who's to say when your language begins to fail you? It doesn't, not all at once. The solution to a problem begins to take longer and becomes more of a challenge to the programmer. But eventually the programming problems became too difficult to solve and to maintain - that is, the solutions were too expensive. It was finally clear that the complexity was more than we could handle. In the general problem, you view the software as providing a service to people. As the needs of the users evolve, that service must evolve with it. Thus a project is not finished when version one ships; it is a living entity that continues to evolve, and the evolution of a program becomes part of the general programming problem. External discipline The need to evolve a program requires new ways of thinking about the problem. This means that a new team member must somehow learn the essentials about a program that previous team members communicated to each other (probably using spoken words). Thus the program needs some form of design documentation. Because documentation is not essential to making a program work, there are no rules for its creation as there are rules imposed by a programming language on a program. Thus, if you require your documentation to satisfy a particular need, you must impose an external discipline. Whether documentation works or not is much more difficult to determine (and requires a program's lifetime to verify), so the best form of external discipline can be more hotly debated than the best programming language. When asking questions about the directions of the future in general, and computing in particular, I start by applying an economic Occam's Razor: Which solution costs less? Assuming the solution satisfies the needs, is the price difference enough to motivate you out of your current, comfortable way of doing things? Taken to an extreme, such a method can conceivably cost as much for program creation and maintenance as the approaches it is intended to replace. At the other end of the external-structure spectrum are the minimalist methods. Perform enough of an analysis to be able to come up with a design, then throw the analysis away so you don't spend time and money maintaining it. Do enough of a design to begin coding, then throw the design away, again, so you don't spend time and money to maintain the document. The code and comments together are enough for the new team member to get up to speed on the project. Because less time is spent with all that tedious documentation (which no one really understands anyway), new members integrate faster. Throwing everything away, however, is probably not the best idea, although if you don't maintain your documents, that's effectively what you do. Some form of document is usually necessary. But it contains the essence of what we really want an external discipline to produce: communication. You'd like to communicate just enough to a new team member that she can help evolve the program. But you'd also like to keep the amount of money you spend on external discipline to a minimum because ultimately people are paying for the service the program provides, not the design documentation behind it. And to be truly useful, the external discipline should do more than just generate documentation - it should be a way for team members to communicate about the design as they're creating it. The goal of the ideal external discipline is to facilitate communication about the analysis and design of a program. This helps the people working on the program now and those who will work on the program in the future. The focus is not just to enable communication, but to create good designs. A successful method (that is, one that gets used) has two important features: 10. It helps you analyze and design. That is, it's much easier to think about and communicate the analysis and design with the method than without it. The difference between your current productivity and the productivity you'll have using the method must be significant; otherwise you might as well stay where you are. Also, it must be simple enough to use that you don't need to carry a handbook. When you're solving your problem, that's what you want to think about, not whether you're using symbols or techniques properly. It doesn't impose overhead without short-term payback. Without some short-term reward in the form of visible progress toward your goal, you aren't going to feel very productive with a method, and you're going to find ways to avoid it. This progress cannot be in the guise of the transformation of one intermediate form to another. You've got to see your classes appear, along with the messages they send each other. Although this attitude is often used to intimidate the unconverted, there is a kernel of truth inside: What you need may depend on the scale of the problem you're attempting to solve. Tiny projects need no external discipline at all other than the patterns of problem solving learned in the lifetime of the individual programmer. Big projects with many people have little communication among those people and so must have a formal way for that communication to occur effectively and accurately. The gray area is the projects in between. Their needs may vary depending on the complexity of the project and the experience of the developers. Certainly all medium-sized projects don't require adherence to a full-blown method, generating many reports, lots of paper, and lots of work. Some probably do, but many can get away with methodology lite (more code, less documentation). The complexity of all the methodologies we are faced with may fall under an 80% - 20% (or less) rule: We are being deluged with details of methodologies that may be needed for less than 20% of the programming problems being solved. If your designs are adequate and maintenance is not a nightmare, maybe you don't need it, or not all of it anyway. Structured OOP? An even more significant question arises. Suppose a methodology is needed to facilitate communication. This meta-communication about the program is necessary because the programming language is inadequate - it is too oriented toward the machine paradigm and is not very helpful for talking about the problem. The procedural-programming model of the world, for example, requires you to talk about a program in terms of data and functions that transform the data. Because this is not the way we discuss the real problem that's being solved, you must translate back and forth between the problem description and the solution description. Once you get a solution description and implement it, proper etiquette requires that you make changes to the problem description anytime you change the solution. This means you must translate from the machine paradigm backward into the problem space. To get a truly maintainable program that can be adapted to changes in the problem space, this is necessary. The overhead and organization required seem to demand an external discipline of some sort. The most important methodology for procedural programming is the structured techniques. Now consider this: What if the language in the solution space were uprooted from the machine paradigm? What if you could force the solution space to use the same terminology as the problem space? For example, an air conditioner in your climate-controlled building becomes an air conditioner in your climate-control program, a thermostat becomes a thermostat, and so on. Conceivably, each phase in the analysis, design, and implementation of a program could use the same terminology, the same representation. This is not just a fanciful argument, as a thought experiment will reveal. Suppose you need to write a little utility, for example, one that performs an operation on a text file like those you'll find in the latter pages of Chapter 5. Some of those took a few minutes to write; the most difficult took a few hours. Now suppose you're back in the 50's and the project must be done in machine language or assembly, with minimal libraries. It goes from a few minutes for one person to weeks or months and many people. In the 50's you'd need a lot of external discipline and management; now you need none. Clearly, the development of tools has greatly increased the complexity of the problems we're able to solve without external discipline (and just as clearly, we go find problems that are more complicated). This is not to suggest that no external discipline is necessary, simply that a useful external discipline for OOP will solve different problems than those solved by a useful external discipline for procedural programming. In particular, the goal of an OOP method must be first and foremost to generate a good design. Not only do good designs of any kind promote reuse, but the need for a good design is directly in line with the needs of developers at all levels of a project. Thus, they will be more likely to adopt such a system. With these points in mind, let's consider some of the issues of an OOP design method. Five stages of object design The design life of an object is not limited to the period of time when you're writing the program. Instead, the design of an object appears to happen over a sequence of stages. It's helpful to have this perspective because you stop expecting perfection right away; instead, you realize that the understanding of what an object does and what it should look like happens over time. The following is a description, not a method. It is simply an observation of when you can expect design of an object to occur. 1. Object discovery This phase occurs during the initial analysis of a program. Objects may be discovered by looking for external factors and boundaries, duplication of elements in the system, and the smallest conceptual units. Some objects are obvious if you already have a set of class libraries. Commonality between classes suggesting base classes and inheritance may appear right away, or later in the design process. 2. Object assembly As you're building an object you'll discover the need for new members that didn't appear during discovery. The internal needs of the object may require new classes to support it. 3. System construction Once again, more requirements for an object may appear at this later stage. As you learn, you evolve your objects. The need for communication and interconnection with other objects in the system may change the needs of your classes or require new classes. 4. System extension As you add new features to a system you may discover that your previous design doesn't support easy system extension. With this new information, you can restructure parts of the system, very possibly adding new classes. 5. Object reuse This is the real stress test for a class. If someone tries to reuse it in an entirely new situation, they'll probably discover some shortcomings. As you change a class to adapt to more new programs, the general principles of the class will become clearer, until you have a truly reusable object. Guidelines for object development These stages suggest some guidelines when thinking about developing your classes: 12. Let a specific problem generate a class, then let the class grow and mature during the solution of other problems. Remember, discovering the classes you need is the majority of the system design. If you already had those classes, this would be a trivial project. Don't force yourself to know everything at the beginning; learn as you go. That's the way it will happen anyway. Start programming; get something working so you can prove or disprove your design. Don't fear procedural-style spaghetti code - classes partition the problem and help control anarchy and entropy. Bad classes do not break good classes. Always keep it simple. Little clean objects with obvious utility are better than big complicated interfaces. You can always start small and simple and expand the class interface when you understand it better. It can be impossible to reduce the interface of an existing class. What a method promises For various reasons methods have often promised a lot more than they can deliver. This is unfortunate because programmers are already a suspicious lot when it comes to strategies and unrealistic expectations; the bad reputation of some methods can cause others to be discarded out of hand. Because of this, valuable techniques can be ignored at significant financial and productivity costs. The best methodology, regardless of what it promises, will solve none of these problems or any problems in the same class. For that matter, OOP and C++ won't help either. Unfortunately, a manager in such a situation is precisely the person that's most vulnerable to the siren song of the silver bullet.12 A tool for productivity This is what a method should be. Increased productivity should come not only in the form of easy and inexpensive maintenance but especially in the creation of a good design in the first place. Because the motivating factor for the creation of methodologies was improved maintenance, some methods ignore the beauty and integrity of the program design in favor of maintenance issues. Instead, a good design should be the foremost goal; a good OOP design will have easy maintenance as a side-effect. A more subtle issue, covered last, is the attitude of the method concerning that most precious of all resources, The enthusiasm of the team members. A communication contract For very small teams, you can keep in such close contact that communication happens naturally. This is the ideal situation. One of the great benefits of C++ is that it allows projects to be built with fewer team members, so this intimate style of communication can be maintained, which means communication overhead is lower and projects can be built more quickly. The situation is not always so ideal. There can come a point where there are too many team members or the project is too complex, and some form of communication discipline is necessary. A method provides a way to form a contract between the members of a team. You can view the concept of such a contract in two ways: 17. Adversarial. The contract is an expression of suspicion between the parties involved, to make sure that no one gets out of line and everyone does what they're supposed to. The contract spells out the bad things that happen if they don't. If you are looking at any contract this way, you've already lost the game because you already think the other party is not trustworthy. If you can't trust someone, a contract won't ensure good behavior. Informational. The contract is an attempt to make sure everyone knows what we've agreed upon. It is an aid to communication so everyone can look at it and say, Yes, that's what I think we're going to do. It's an expression of an agreement after the agreement has been made, just to clean up misunderstandings. This sort of contract can be minimalist and easy to read. A useful method will not foment an adversarial contract; the emphasis will be on communication. A structuring system The structure is the heart of your system. If a method accomplishes nothing else it must be able to tell programmers: 19. What classes you need. How you hook them together to build a working system. A method generates these answers through a process that begins with an analysis of the problem and ends with some sort of representation of the classes, the system, and the messages passed between the classes in the system. Tools for representation The model should not be more complex than the system it represents. A good model presents an abstraction. You are certainly not constrained to using the representation tools that come with a particular method. You can make up your own to suit your needs. Include no more detail than necessary. Remember the seven plus or minus two rule of complexity. You should be able to get as much information as you need by probing deeper into the representation levels. That is, levels can be created if necessary, hidden at higher levels of abstraction and made visible on demand. The notation should be as minimal as possible. Too much magic causes software rot. 24. System design and class design are separate issues. Classes are reusable tools, while systems are solutions to specific problems (although a system design, too, may be reusable). The notation should focus first on system design. Is a class design notation necessary? The expression of classes provided by the C++ language seems to be adequate for most situations. If a notation doesn't give you a significant boost over describing classes in their native language, then it's a hindrance. The notation should hide the implementation internals of the objects. Those are generally not important during design. Keep it simple. The analysis is the design. Basically, all you want to do in your method is discover your objects and how they connect with each other to form a system. If a method and notation require more from you, then you should question whether that method is spending your time wisely. It seems that no matter how thorny the problem, how badly you've failed in the past, the primitiveness of your tools or what the odds are, enthusiasm can overcome the obstacle. This sort of thinking has the effect of damping the enthusiasm of the team, because they can feel like no more than a means to a company's profit motive, a cog. Once this happens a team member becomes an employee, watching the clock and seeking interesting distractions. A method and management technique built upon motivation and enthusiasm as the most precious resources would be an interesting experiment indeed. At least, you should consider the effect that an OOP design method will have on the morale of your team members. Required reading Before you choose any method, it's helpful to gain perspective from those who are not trying to sell one. It's easy to adopt a method without really understanding what you want out of it or what it will do for you. Others are using it, which seems a compelling reason. However, humans have a strange little psychological quirk: If they want to believe something will solve their problems, they'll try it. Everything's wonderful, we don't need to change). I think the following books, read before you choose a method, will provide you with these tools. Software Creativity, by Robert Glass (Prentice-Hall, 1995). This is the best book I've seen that discusses perspective on the whole methodology issue. It's a collection of short essays and papers that Glass has written and sometimes acquired (P.J. Plauger is one contributor), reflecting his many years of thinking and study on the subject. They're entertaining and only long enough to say what's necessary; he doesn't ramble and lose your interest. He's not just blowing smoke, either; there are hundreds of references to other papers and studies. All programmers and managers should read this book before wading into the methodology mire.13 Peopleware, by Tom Demarco and Timothy Lister (Dorset House, 1987). Although they have backgrounds in software development, this book is about projects and teams in general. But the focus is on the people and their needs rather than the technology and its needs. They talk about creating an environment where people will be happy and productive, rather than deciding what rules those people should follow to be adequate components of a machine. This latter attitude, I think, is the biggest contributor to programmers smiling and nodding when XYZ method is adopted and then quietly doing whatever they've always done. Complexity, by M. Mitchell Waldrop (Simon and Schuster, 1992). Scripting: a minimal method I'll start by saying this is not tried or tested anywhere. I make no promises - it's a starting point, a seed for other ideas, and a thought experiment, albeit after a great deal of thought and a fair amount of reading and observation of myself and others in the process of development. It was inspired by a writing class I took called Story Structure, taught by Robert McKee,14 primarily to aspiring and practicing screenwriters, but also for novelists and playwrights. There are a few amazingly well-told stories, many stories that are uninspired but competent and get the job done, and a lot of badly told stories, some of which don't get published. Of course, stories seem to want to be told while programs demand to be written. Writers have an additional constraint that does not always appear in programming: They generally work alone or possibly in groups of two. Thus they must be very economical with their time, and any method that does not bear significant fruit is discarded. Two of McKee's goals were to reduce the typical amount of time spent on a screenplay from one year to six months and to significantly increase the quality of the screenplays in the process. Similar goals are shared by software developers. Getting everyone to agree on anything is an especially tough part of the startup process of a project. The minimal nature of this system should win over even the most independent of programmers. Premises I'm basing the method described here on two significant premises, which you must carefully consider before you adopt the rest of the ideas: 28. C++, unlike typical procedural languages (and most existing languages, for that matter) has many guards in the language and language features so you can build in your own guards. These guards are intended to prevent the program you create from losing its structure, both during the process of creating it and over time, as the program is maintained. No matter how much analysis you do, there are some things about a system that won't reveal themselves until design time, and more things that won't reveal themselves until a program is up and running. Because of this, it's critical to move fairly quickly through analysis and design to implement a test of the proposed system. Because of Point 1, this is far safer than when using procedural languages, because the guards in C++ are instrumental in preventing the creation of spaghetti code. This second point is worth emphasizing. Because of the history we've had with procedural languages, it is commendable that a team will want to proceed carefully and understand every minute detail before moving to design and implementation. Certainly, when creating a DBMS, it pays to understand a customer's needs thoroughly. But a DBMS is in a class of problems that is very well-posed and well-understood. Solving such a problem requires iteration through the whole cycle, and that requires risk-taking behavior (which makes sense, because you're trying to do something new and the potential rewards are higher). It may seem like the risk is compounded by rushing into a preliminary implementation, but it can instead reduce the risk in a wild-card project because you're finding out early whether a particular design is viable. The goal of this method is to attack wild-card projects by producing the most rapid development of a proposed solution, so the design can be proved or disproved as early as possible. Your efforts will not be lost. Thus, the first rapid pass at a problem not only produces critical information for the next analysis, design, and implementation iteration, it also creates a code foundation for that iteration. Another important feature of this method is support for brainstorming at the early part of a project. By keeping the initial document small and concise, it can be created in a few sessions of group brainstorming with a leader who dynamically creates the description. This not only solicits input from everyone, it also fosters initial buy-in and agreement by everyone on the team. Perhaps most importantly, it can kick off a project with a lot of enthusiasm (as noted previously, the most essential resource). Representation The writer's most valuable computer tool is the word processor, because it easily supports the structure of a document. With programming projects, the structure of the program is usually supported and described by some form of separate documentation. As the projects become more complex, the documentation is essential. This raises a classic problem, stated by Brooks:16 A basic principle of data processing teaches the folly of trying to maintain independent files in synchronism.... Yet our practice in programming documentation violates our own teaching. We typically attempt to maintain a machine-readable form of a program and an independent set of human-readable documentation .... A good tool will connect the code and its documentation. I consider it very important to use familiar tools and modes of thinking; the change to OOP is challenging enough by itself. Early OOP methodologies have suffered by using elaborate graphical notation schemes. You inevitably change your design a lot, so expressing it with a notation that's difficult to modify is a liability because you'll resist changing it to avoid the effort involved. Only recently have tools been appearing that manipulate these graphical notations. Tools for easy use of a design notation must already be in place before you can expect people to use a method. This follows the spirit of C++, where you build on your existing knowledge and tool base rather than throwing it away. The mode of thinking used by this method also follows that spirit. Although a graphical notation is useful18 to express a design in a report, it is not fast enough to support brainstorming. However, everyone understands outlining, and most word processors have some sort of outlining mode that allows you to grab pieces of the outline and quickly move them around. This is perfect for rapid design evolution in an interactive brainstorming session. In addition, you can expand and collapse outlines to see various levels of granularity in the system. And (as described later), as you create the design, you create the design document, so a report on the state of the project can be produced with a process not unlike running a compiler. 1. High concept Any system you build, no matter how complicated, has a fundamental purpose, the business that it's in, the basic need that it satisfies. If you can look past the user interface, the hardware- or system-specific details, the coding algorithms and the efficiency problems, you will eventually find the core of its being, simple and straightforward. Like the so-called high concept from a Hollywood movie, you can describe it in one or two sentences. This pure description is the starting point. The high concept is quite important because it sets the tone for your project; it's a mission statement. You won't necessarily get it right the first time (you may be developing the treatment or building the design before it becomes completely clear), but keep trying until it feels right. The best way to develop the high concept and treatment for a computer system may be in a group situation with a facilitator who has writing ability. Ideas can be suggested in a brainstorming environment, while the facilitator tries to express the ideas on a computer that's networked with the group or projected on screen. The facilitator takes the role of a ghostwriter and doesn't judge the ideas but instead simply tries to make them clear and keep them flowing. The treatment becomes the jumping-off point for the initial object discovery and first rough cut at design, which can also be performed in a group setting with a facilitator. 3. Structuring Structure is the key to the system. Without structure you have a random collection of meaningless events. With structure you have a story. The structure of a story is expressed through characters, which correspond to objects, and plot, which corresponds to system design. Organizing the system As mentioned earlier, the primary representation tool for this method is a sophisticated word processor with outlining facility. You start with level-1 sections for high concept, treatment, objects, and design. As the objects are discovered, they are placed as level-2 subsections under objects. Object interfaces are added as level-3 subsections under the specific type of object. If essential descriptive text comes up, it is placed as normal text under the appropriate subsection. Because this technique involves typing and outlining, with no drawing, the brainstorming process is not hindered by the speed of creating the representation. Characters: initial object discovery The treatment contains nouns and verbs. As you find these, the nouns will suggest classes, and the verbs will become either methods for those classes or processes in the system design. Although you may not be comfortable that you've found everything after this first pass, remember that it's an iterative process. You can add additional classes and methods at further stages and later design passes, as you understand the problem better. The point of this structuring is that you don't currently understand the problem, so don't expect the design to be revealed to you all at once. Start by simply moving through the treatment and creating a level-2 subsection in objects for each unique noun that you find. Take verbs that are clearly acting upon an object and place them as level-3 method subsections beneath the appropriate noun. Add the argument list (even if it's initially empty) and return type for each method. This will give you a rough cut and something to talk about and push around. This allows the code to be properly generated. If expressed, they should appear as text-level notes beneath the appropriate class. When decision points come up, use a modified Occam's Razor approach: Consider the choices and select the one that is simplest, because simple classes are almost always best. It's easy to add more elements to a class, but as time goes on, it's difficult to take them away. If you need to seed the process, look at the problem from a lazy programmer's standpoint: What objects would you like to magically appear to solve your problem? It's also helpful to have references on hand for the classes that are available and the various system design patterns, to clarify proposed classes or designs. You won't stay in the objects section the entire time; instead, you'll move back and forth between objects and system design as you analyze the treatment. Also, at any time you may want to write some normal text beneath any of the subsections as ideas or notes about a particular class or method. Plot: initial system design From the high concept and treatment, a number of subplots should be apparent. Often they may be as simple as input, process, output, or user interface, actions. Each subplot has its own level-2 subsection under design. Most stories follow one of a set of common plots; in OOP the analogy is being called a pattern. Refer to resources on OOP design patterns to aid in searching for plots. At this point, you're just trying to create a rough sketch of the system. During the brainstorming session, people in the group make suggestions about activities they think occur in the system, and each activity is recorded individually, without necessarily working to connect it to the whole. A subplot will have a set of stages or states that it moves through, conditions for moving between stages, and the actions involved in each transition. Each stage is given its own level-3 subsection under that particular subplot. The conditions and transitions can be described as text under the stage subhead. Ideally, you'll eventually (as the design iteration proceeds) be able to write the essentials of each subplot as the creation of objects and sending messages to them. This becomes the initial code body for that subplot. The design discovery and object discovery processes will stimulate each other, so you'll be adding subentries to both sections during the session. 4. Development This is the initial conversion from the rough design to a compiling body of code that can be tested, and especially that will prove or disprove your design. This way, generating design documentation after coding begins (and the inevitable changes occur) becomes reasonably effortless, and the design document can become a tool for reporting on the progress of the project. Initial translation By using the standard section names objects and design at level-1 section headings, you can key your tools to lift out those sections and generate your header files from them. You perform different activities depending on what major section you're in and the level of subsection you're working on. The easiest approach may be to have your tool or macro break the document into pieces and work on each one appropriately. Your tool will simply move through these and create the class declarations. For simplicity, a single class declaration will appear in each header file. The best approach to naming the header files is probably to include the file name as tagged information in the level-2 section name for that class. Plotting can be more subtle. Each subplot may produce an independent function, called from inside main( ), or simply a section in main( ). Start with something that gets the job done; a more refined pattern may emerge in future iterations. Code generation Using automatic tools (most word-processor scripting tools are adequate for this), 30. Generate a header file for each subplot and copy its description as a commented block at the beginning of the file, followed by function declarations. Class names and function declarations also retain comment markers. This way, a reversing tool can go through, extract all the information and regenerate the source document, preferably, in a document-description language like Rich Text Format (RTF). The interfaces and plots should be compilable at this point (but not linkable), so syntax checking can occur. This will ensure the high-level integrity of the design. The document can be regenerated from the correctly compiling files. At this point, two things can happen. If the design is still very early, it's probably easiest to work on the document (rather than the code) in brainstorming sessions, or on subparts of the document in groups responsible for them. However, if the design is complete enough, you can begin coding. If interface elements are added during coding, they must be tagged by the programmer along with tagged comments, so the regeneration program can use the new information to produce the document. If you had the front end to a compiler, you could certainly do this for classes and functions automatically, but that's a big job and the language is evolving. Using explicit tags is fairly fail-safe, and commercial browsing tools can be used to verify that all public functions have made it into the document (that is, they were tagged). 5. Rewriting This is the analogy of rewriting a screenplay to refine it and make it shine. In programming, it's the process of iteration. It's where your program goes from good to great, and where those issues that you didn't really understand in the first pass become clear. It's also where your classes can evolve from single-project usage to reusable resources. From a tool standpoint, reversing the process is a bit more complicated. You want to be able to decompose the header files so they can be reintegrated into the design document, including all the changes that have been made during coding. Then, if any changes are made to the design in the design document, the header files must be completely rebuilt, without losing any of the work that was done to get the header file to compile in the first iteration. Thus, your tool must not only look for your tagged information to turn into section levels and text, it must also find, tag, and store the other information such as the #includes at the beginning of each file. If you keep in mind that the header file expresses the class design and that you must be able to regenerate the header from your design document, you'll be OK. Also notice that the text level notes and discussions, which were turned into tagged comments on the initial generation, have more than likely been modified by the programmer as the design evolved. It's essential that these are captured and put into their respective places, so the design document reflects the new information. This allows you to change that information, and it's carried back to the generated header files. You have to know when to stop when iterating the design. Ideally, you achieve target functionality and are in the process of refinement and addition of new features when the deadline comes along and forces you to stop and ship that version. This process can be painless if it's done over a network using automatic tools. Regularly integrating and maintaining the master design document is the responsibility of the project leader or manager, while teams or individuals are responsible for subparts of the document (that is, their code and comments). Supplemental features, such as class diagrams, can be generated using third-party tools and automatically included in the document. A current report can be generated at any time by simply refreshing the document. The state of all parts of the program can then be viewed; this also provides immediate updates for support groups, especially end-user documentation. The document is also critically valuable for rapid start-up of new team members. A single document is more reasonable than all the documents produced by some analysis, design, and implementation methods. Although one smaller document is less impressive, it's alive, whereas an analysis document, for example, is only valuable for a particular phase of the project and then rapidly becomes obsolete. It's hard to put a lot of effort into a document that you know will be thrown away. Analysis and design The object-oriented paradigm is a new and different way of thinking about programming and many folks have trouble at first knowing how to approach a project. Now that you know that everything is supposed to be an object, you can create a good design, one that will take advantage of all the benefits that OOP has to offer. Books on OOP analysis and design are coming out of the woodwork. So, hoping I've built a healthy skepticism within you, I shall endeavor to give you my own perspective on analysis and design in as few paragraphs as possible. Staying on course While you're going through the development process, the most important issue is this: don't get lost. It's easy to do. Most of these methodologies are designed to solve the largest of problems. But some sort of process, no matter how limited, will generally get you on your way in a much better fashion than simply beginning to code. That said, if you're looking at a methodology that contains tremendous detail and suggests many steps and documents, it's still difficult to know when to stop. Keep in mind what you're trying to discover: 1. What are the objects? For various reasons you might need more descriptions and documents than this, but you can't really get away with any less. The process can be undertaken in four phases, and a phase 0 which is just the initial commitment to using some kind of structure. Phase 0: Let's make a plan The first step is to decide what steps you're going to have in your process. It sounds simple (in fact, all of this sounds simple) and yet, often, people don't even get around to phase one before they start coding. If your plan is let's jump in and start coding, fine. You might also decide at this phase that some additional process structure is necessary but not the whole nine yards. When I began to study story structure (so that I will someday write a novel) I was initially resistant to the idea, feeling that when I wrote I simply let it flow onto the page. What I found was that when I wrote about computers the structure was simple enough so I didn't need to think much about it, but I was still structuring my work, albeit only semi-consciously in my head. So even if you think that your plan is to just start coding, you still go through the following phases while asking and answering certain questions. Phase 1: What are we making? Their intention was good, however. The system specification is a top-level exploration into the problem and in some sense a discovery of whether it can be done and how long it will take. Since both of these will require consensus among people, I think it's best to keep them as bare as possible - ideally, to lists and basic diagrams - to save time. You might have other constraints that require you to expand them into bigger documents. It's necessary to stay focused on the heart of what you're trying to accomplish in this phase: determine what the system is supposed to do. You try to discover a full set of use-cases for your system, and once you've done that you've got the core of what the system is supposed to do. The nice thing about focusing on use-cases is that they always bring you back to the essentials and keep you from drifting off into issues that aren't critical for getting the job done. That is, if you have a full set of use-cases you can describe your system and move onto the next phase. You probably won't get it all figured out perfectly at this phase, but that's OK. Everything will reveal itself in the fullness of time, and if you demand a perfect system specification at this point you'll get stuck. It helps to kick-start this phase by describing the system in a few paragraphs and then looking for nouns and verbs. The nouns become the objects and the verbs become the methods in the object interfaces. You'll be surprised at how useful a tool this can be; sometimes it will accomplish the lion's share of the work for you. Although it's a black art, at this point some kind of scheduling can be quite useful. You now have an overview of what you're building so you'll probably be able to get some idea of how long it will take. A lot of factors come into play here: if you estimate a long schedule then the company might not decide to build it, or a manager might have already decided how long the project should take and will try to influence your estimate. But it's best to have an honest schedule from the beginning and deal with the tough decisions early. There have been a lot of attempts to come up with accurate scheduling techniques (like techniques to predict the stock market), but probably the best approach is to rely on your experience and intuition. Get a gut feeling for how long it will really take, then double that and add 10 percent. Your gut feeling is probably correct; you can get something working in that time. The doubling will turn that into something decent, and the 10 percent will deal with final polishing and details. However you want to explain it, and regardless of the moans and manipulations that happen when you reveal such a schedule, it just seems to work out that way. Phase 2: How will we build it? In this phase you must come up with a design that describes what the classes look like and how they will interact. A useful diagramming tool that has evolved over time is the Unified Modeling Language (UML). You can get the specification for UML at www.rational.com. UML can also be helpful as a descriptive tool during phase 1, and some of the diagrams you create there will probably show up unmodified in phase 2. You don't need to use UML, but it can be helpful, especially if you want to put a diagram up on the wall for everyone to ponder, which is a good idea. An alternative to UML is a textual description of the objects and their interfaces (as I described in Thinking in C++), but this can be limiting. The most successful consulting experiences I've had when coming up with an initial design involves standing in front of a team, who hadn't built an OOP project before, and drawing objects on a whiteboard. We talked about how the objects should communicate with each other, and erased some of them and replaced them with other objects. The team (who knew what the project was supposed to do) actually created the design; they owned the design rather than having it given to them. All I was doing was guiding the process by asking the right questions, trying out the assumptions and taking the feedback from the team to modify those assumptions. The true beauty of the process was that the team learned how to do object-oriented design not by reviewing abstract examples, but by working on the one design that was most interesting to them at that moment: theirs. You'll know you're done with phase 2 when you have described the objects and their interfaces. Well, most of them - there are usually a few that slip through the cracks and don't make themselves known until phase 3. But that's OK. All you are concerned with is that you eventually discover all of your objects. It's nice to discover them early in the process but OOP provides enough structure so that it's not so bad if you discover them later. Phase 3: Let's build it! If you're reading this book you're probably a programmer, so now we're at the part you've been trying to get to. Getting code to run and do what you want is fulfilling, even like some kind of drug if you look at the obsessive behavior of some programmers. But it's my experience that coming up with an elegant solution is deeply satisfying at an entirely different level; it feels closer to art than technology. And elegance always pays off; it's not a frivolous pursuit. Not only does it give you a program that's easier to build and debug, but it's also easier to understand and maintain, and that's where the financial value lies. After you build the system and get it running, it's important to do a reality check, and here's where the requirements analysis and system specification comes in. Go through your program and make sure that all the requirements are checked off, and that all the use-cases work the way they're described. Now you're done. Or are you? Perhaps there's a better term to describe what's going on. The term is iteration. That is, You won't get it right the first time, so give yourself the latitude to learn and to go back and make changes. You might need to make a lot of changes as you learn and understand the problem more deeply. The elegance you'll produce if you iterate until you've got it right will pay off, both in the short and the long run. What it means to get it right isn't just that the program works according to the requirements and the use-cases. It also means that the internal structure of the code makes sense to you, and feels like it fits together well, with no awkward syntax, oversized objects or ungainly exposed bits of code. In addition, you must have some sense that the program structure will survive the changes that it will inevitably go through during its lifetime, and that those changes can be made easily and cleanly. This is no small feat. You must not only understand what you're building, but also how the program will evolve (what I call the vector of change). Fortunately, object-oriented programming languages are particularly adept at supporting this kind of continuing modification - the boundaries created by the objects are what tend to keep the structure from breaking down. They are also what allow you to make changes that would seem drastic in a procedural program without causing earthquakes throughout your code. In fact, support for iteration might be the most important benefit of OOP. With iteration, you create something that at least approximates what you think you're building, and then you kick the tires, compare it to your requirements and see where it falls short. When you see the system, you realize you want to solve a different problem. If you think this kind of iteration is going to happen, then you owe it to yourself to build your first version as quickly as possible so you can find out if it's what you want. Iteration is closely tied to incremental development. Incremental development means that you start with the core of your system and implement it as a framework upon which to build the rest of the system piece by piece. Then you start adding features one at a time. The trick to this is in designing a framework that will accommodate all the features you plan to add to it. Also, new features that are incorporated later in the development or maintenance phases can be added more easily. OOP supports incremental development because if your program is designed well, your increments will turn out to be discrete objects or groups of objects. Plans pay off Of course you wouldn't build a house without a lot of carefully-drawn plans. If you build a deck or a dog house, your plans won't be so elaborate but you'll still probably start with some kind of sketches to guide you on your way. Software development has gone to extremes. For a long time, people didn't have much structure in their development, but then big projects began failing. In reaction, we ended up with methodologies that had an intimidating amount of structure and detail. These were too scary to use - it looked like you'd spend all your time writing documents and no time programming. Use an approach that fits your needs (and your personality). No matter how minimal you choose to make it, some kind of plan will make a big improvement in your project as opposed to no plan at all. Remember that, by some estimates, over 50 percent of projects fail. Other methods There are currently a large number (more than 20) of formal methods available for you to choose from.21 Some are not entirely independent because they share fundamental ideas, but at some higher level they are all unique. Because at the lowest levels most of the methods are constrained by the default behavior of the language, each method would probably suffice for a simple project. The true benefit is claimed to be at the higher levels; one method may excel at the design of real-time hardware controllers, but that method may not as easily fit the design of an archival database. The following descriptions of three of the most popular methods are mainly for flavor, not comparison shopping. If you want to learn more about methods, there are many books and courses available. Booch The Booch22 method is one of the original, most basic, and most widely referenced. Because it was developed early, it was meant to be applied to a variety of programming problems. It focuses on the unique features of OOP: classes, methods, and inheritance. The steps are as follows: 35. Identify classes and objects at a certain level of abstraction. This is predictably a small step. You state the problem and solution in natural language and identify key features such as nouns that will form the basis for classes. If you're in the fireworks business, you may want to identify Workers, Firecrackers, and Customers; more specifically you'll need Chemists, Assemblers, and Handlers; AmateurFirecrackers and ProfessionalFirecrackers; Buyers and Spectators. Even more specifically, you could identify YoungSpectators, OldSpectators, TeenageSpectators, and ParentSpectators. Identify their semantics. Define classes at an appropriate level of abstraction. If you plan to create a class, you should identify that class's audience properly. For example, if you create a class Firecracker, who is going to observe it, a Chemist or a Spectator? The former will want to know what chemicals go into the construction, and the latter will respond to the colors and shapes released when it explodes. Identify relationships between them (CRC cards). Define how the classes interact with other classes. A common method for tabulating the information about each class uses the Class, Responsibility, Collaboration (CRC) card. This is a small card (usually an index card) on which you write the state variables for the class, the responsibilities it has (i.e., the messages it gives and receives), and references to the other classes with which it interacts. Why an index card? The reasoning is that if you can't fit all you need to know about a class on a small card, the class is too complex. The ideal class should be understood at a glance; index cards are not only readily available, they also happen to hold what most people consider a reasonable amount of information. A solution that doesn't involve a major technical innovation is one that's available to everyone (like the document structuring in the scripting method described earlier in this chapter). Implement the classes. Now that you know what to do, jump in and code it. In most projects the coding will affect the design. Iterate the design. The design process up to this point has the feeling of the classic waterfall method of program development. Now it diverges. After a preliminary pass to see whether the key abstractions allow the classes to be separated cleanly, iterations of the first three steps may be necessary. Booch writes of a round-trip gestalt design process. Having a gestalt view of the program should not be impossible if the classes truly reflect the natural language of the solution. Perhaps the most important thing to remember is that by default - by definition, really - if you modify a class its super- and subclasses will still function. A glance at your CRC card for the class will probably be the only clue you need to verify the new version. Responsibility-Driven Design (RDD) This method23 also uses CRC cards. Here, as the name implies, the cards focus on delegation of responsibilities rather than appearance. To illustrate, the Booch method might produce an Employee-BankEmployee-BankManager hierarchy; in RDD this might come out Manager-FinanceManager-BankManager. The bank manager's primary responsibilities are managerial, so the hierarchy reflects that. More formally, RDD involves the following: 40. Data or state. A description of the data or state variables for each class. Sinks and sources. Identification of data sinks and sources, classes that process or generate data. Observer or view. View or observer classes that separate hardware dependencies. Facilitator or helper. Facilitator or helper classes, such as a linked list, that contain little or no state information and simply help other classes to function. Object Modeling Technique (OMT) Object Modeling Technique24 (OMT) adds one more level of complexity to the process. Booch's method emphasizes the fundamental appearance of classes and defines them simply as outgrowths of the natural language solution. RDD takes that one step further by emphasizing the class responsibility more than its appearance. OMT describes not only the classes but various states of the system using detailed diagramming, as follows: 44. Object model, what, object diagram. The object model is similar to that produced by Booch's method and RDD. Object classes are connected by responsibilities. Dynamic model, when, state diagram. The dynamic model describes time-dependent states of the system. Different states are connected by transitions. An example that contains time-dependent states is a real-time sensor that collects data from the outside world. Functional model, how, data flow diagram. The functional model traces the flow of data. The theory is that because the real work at the lowest level of the program is accomplished using procedures, the low-level behavior of the program is best understood by diagramming the data flow rather than by diagramming its objects. This may be true, in the long run. But in the short run, a lot of that baggage was valuable. The most valuable elements may not be the existing code base (which, given adequate tools, could be translated), but instead the existing mind base. If you're a functioning C programmer and must drop everything you know about C in order to adopt a new language, you immediately become nonproductive for many months, until your mind fits around the new paradigm. Whereas if you can leverage off of your existing C knowledge and expand upon it, you can continue to be productive with what you already know while moving into the world of object-oriented programming. So the reason for the success of C++, in a nutshell, is economic: It still costs to move to OOP, but C++ costs a lot less. The goal of C++ is improved productivity. This productivity comes in many ways, but the language is designed to aid you as much as possible, while hindering you as little as possible with arbitrary rules or any requirement that you use a particular set of features. The reason C++ is successful is that it is designed with practicality in mind: Decisions are based on providing the maximum benefits to the programmer. A better C You get an instant win even if you continue to write C code because C++ has closed the holes in the C language and provides better type checking and compile-time analysis. You're forced to declare functions so the compiler can check their use. The preprocessor has virtually been eliminated for value substitution and macros, which removes a set of difficult-to-find bugs. C++ has a feature called references that allows more convenient handling of addresses for function arguments and return values. The handling of names is improved through function overloading, which allows you to use the same name for different functions. Namespaces also improve the control of names. There are numerous other small features that improve the safety of C. You're already on the learning curve The problem with learning a new language is productivity: No company can afford to suddenly lose a productive software engineer because she's learning a new language. C++ is an extension to C, not a complete new syntax and programming model. It allows you to continue creating useful code, applying the features gradually as you learn and understand them. This may be one of the most important reasons for the success of C++. In addition, all your existing C code is still viable in C++, but because the C++ compiler is pickier, you'll often find hidden errors when recompiling the code. Efficiency Sometimes it is appropriate to trade execution speed for programmer productivity. A financial model, for example, may be useful for only a short period of time, so it's more important to create the model rapidly than to execute it rapidly. However, most applications require some degree of efficiency, so C++ always errs on the side of greater efficiency. Because C programmers tend to be very efficiency-conscious, this is also a way to ensure they won't be able to argue that the language is too fat and slow. A number of features in C++ are intended to allow you to tune for performance when the generated code isn't efficient enough. The design produced for an OOP program may actually be more efficient than the C counterpart. Systems are easier to express and understand Classes designed to fit the problem tend to express it better. You deal with higher-level concepts and can do much more with a single line of code. The other benefit of this ease of expression is maintenance, which (if reports can be believed) takes a huge portion of the cost over a program's lifetime. If a program is easier to understand, then it's easier to maintain. This can also reduce the cost of creating and maintaining the documentation. Maximal leverage with libraries The fastest way to create a program is to use code that's already written: a library. A major goal in C++ is to make library use easier. This is accomplished by casting libraries into new data types (classes), so bringing in a library is adding a new data type to the language. Because the compiler takes care of how the library is used - guaranteeing proper initialization and cleanup, ensuring functions are called properly - you can focus on what you want the library to do, not how you have to do it. Because names can be sequestered to portions of your program, you can use as many libraries as you want without the kinds of name clashes you'd run into with C. Source-code reuse with templates There is a significant class of types that require source-code modification in order to reuse them effectively. The template performs the source code modification automatically, making it an especially powerful tool for reusing library code. A type you design using templates will work effortlessly with many other types. Templates are especially nice because they hide the complexity of this type of code reuse from the client programmer. Error handling Error handling in C is a notorious problem, and one that is often ignored - finger-crossing is usually involved. If you're building a large, complex program, there's nothing worse than having an error buried somewhere with no vector telling you where it came from. C++ exception handling (the subject of Chapter 16) is a way to guarantee that an error is noticed and that something happens as a result. Programming in the large Many traditional languages have built-in limitations to program size and complexity. C, too, has these limitations. For example, when a program gets beyond perhaps 50,000 lines of code, name collisions start to become a problem. In short, you run out of function and variable names. Another particularly bad problem is the little holes in the C language - errors can get buried in a large program that are extremely difficult to find. There's no clear line that tells when your language is failing you, and even if there were, you'd ignore it. You don't say, My BASIC program just got too big; I'll have to rewrite it in C! Instead, you try to shoehorn a few more lines in to add that one extra feature. So the extra costs come creeping up on you. C++ is designed to aid programming in the large, that is, to erase those creeping-complexity boundaries between a small program and a large one. You certainly don't need to use OOP, templates, namespaces, and exception handling when you're writing a hello-world-class utility program, but those features are there when you need them. And the compiler is aggressive about ferreting out bug-producing errors for small and large programs alike. You've done it before. First comes education and examples; then comes a trial project to give you a feel for the basics without doing anything too confusing; then you try to do a real world project that actually does something useful. Throughout your first projects you continue your education by reading, asking questions of gurus, and trading hints with friends. In essence, this is the approach many authors suggest for the switch from C to C++. Switching an entire company will of course introduce certain group dynamics, but it will help at each step to remember how one person would do it. Stepping up to OOP Here are some guidelines to consider when making the transition to OOP and C++: 1. Training The first step is some form of education. Remember the company's investment in plain C code, and try not to throw it all into disarray for 6 to 9 months while everyone puzzles over how multiple inheritance works. This is especially good for smaller companies making fundamental shifts in the way they do things, or at the division level of larger companies. Because the cost is higher, however, some may choose to start with project-level training, do a pilot project (possibly with an outside mentor), and let the project team become the teachers for the rest of the company. 2. Low-risk project Try a low-risk project first and allow for mistakes. Once you've gained some experience, you can either seed other projects from members of this first team or use the team members as an OOP technical support staff. This first project may not work right the first time, so it should be not very important in the grand scheme of things. There's a good probability that someone has solved your problem already, and if they haven't solved it exactly you can probably apply what you've learned about abstraction to modify an existing design to fit your needs. However, some new programmers don't understand this, are unaware of existing class libraries, or through fascination with the language desire to write classes that may already exist. Your success with OOP and C++ will be optimized if you make an effort to seek out and reuse other people's code early in the transition process. But chances are you aren't going to see the dramatic increases in productivity that you hope for in your first few projects unless that project is a new one. C++ and OOP shine best when taking a project from concept to reality. Moving to C++ falls in all three of these categories, and it would be wonderful if it didn't cost you anything as well. Startup costs The cost is more than just the acquisition of a C++ compiler. These are hard-money costs that must be factored into a realistic proposal. In addition, there are the hidden costs in loss of productivity while learning a new language and possibly a new programming environment. Training and mentoring can certainly minimize these but team members must overcome their own struggles to understand the issues. During this process they will make more mistakes (this is a feature, because acknowledged mistakes are the fastest path to learning) and be less productive. Thus, they virtually guaranteed a significant increase in size and decrease in speed. C++, however, is designed with production programming in mind. When your focus is on rapid prototyping, you can throw together components as fast as possible while ignoring efficiency issues. If you're using any third-party libraries, these are usually already optimized by their vendors; in any case it's not an issue while you're in rapid-development mode. When you have a system you like, if it's small and fast enough, then you're done. If not, you begin tuning with a profiling tool, looking first for speedups that can be done with simple applications of built-in C++ features. If that doesn't help, you look for modifications that can be made in the underlying implementation so no code that uses a particular class needs to be changed. Only if nothing else solves the problem do you need to change the design. The fact that performance in that portion of the design is so critical is an indicator that it must be part of the primary design criteria. You have the benefit of finding this out early through rapid prototyping. As mentioned earlier in this chapter, the number that is most often given for the difference in size and speed between C and C++ is ±10%, and often much closer to par. You may actually get a significant improvement in size and speed for C++ over C because the design you make for C++ could be quite different from the one you'd make for C. The evidence for size and speed comparisons between C and C++ is so far all anecdotal and is likely to remain so. Regardless of the number of people who suggest that a company try the same project using C and C++, no company is likely to waste money that way, unless it's very big and interested in such research projects. Even then it seems like the money could be better spent. Common design errors When starting your team into OOP and C++, programmers will typically go through a series of common design errors. This often happens because of too little feedback from experts during the design and implementation of early projects, because no experts have been developed within the company. It's easy to feel that you understand OOP too early in the cycle and go off on a bad tangent; something that's obvious to someone experienced with the language may be a subject of great internal debate for a novice. Much of this trauma can be skipped by using an outside expert for training and mentoring. It's important to evaluate your own needs and decide whether C++ will optimally satisfy those needs, or if you might be better off with another programming system. If you know that your needs will be very specialized for the foreseeable future and if you have specific constraints that may not be satisfied by C++, then you owe it to yourself to investigate the alternatives. Even if you eventually choose C++ as your language, you'll at least understand what the options were and have a clear vision of why you took that direction. 2: Making and using objects This chapter will introduce enough of the concepts of C++ and program construction to allow you to write and run a simple object-oriented program. In the following chapter we will cover the basic syntax of C and C++ in detail. Classes that someone else has created are often packaged into a library. This chapter uses the iostream library of classes, which comes with all C++ implementations. Iostreams are a very useful way to read from files and the keyboard, and to write to files and the display. After covering the basics of building a program in C and C++, iostreams will be used to show how easy it is to utilize a pre-defined library of classes. To create your first program you must understand the tools used to build applications. The process of language translation All computer languages are translated from something that tends to be easy for a human to understand (source code) into something that is executed on a computer (machine instructions). Traditionally, translators fall into two classes: interpreters and compilers. Interpreters An interpreter translates source code (written in the programming language) into activities (which may comprise groups of machine instructions) and immediately executes those activities. BASIC is the most popular interpreted language. BASIC interpreters translate and execute one line at a time, and then forget the line has been translated. This makes them slow, since they must re-translate any repeated code. More modern interpreters translate the entire program into an intermediate language, that is executed by a much faster interpreter. Interpreters have many advantages. The transition from writing code to executing code is almost immediate, and the source code is always available so the interpreter can be much more specific when an error occurs. The benefits often cited for interpreters are ease of interaction and rapid development (but not execution) of programs. Interpreters usually have severe limitations when building large projects. The interpreter (or a reduced version) must always be in memory to execute the code, and even the fastest interpreter may introduce unacceptable speed restrictions. Most interpreters require that the complete source code be brought into the interpreter all at once. Not only does this introduce a space limitation, it can also cause more difficult bugs if the language doesn't provide facilities to localize the effect of different pieces of code. Compilers A compiler translates source code directly into assembly language or machine instructions. This is an involved process, and usually takes several steps. The transition from writing code to executing code is significantly longer with a compiler. Depending on the acumen of the compiler writer, programs generated by a compiler tend to require much less space to run, and run much more quickly. Although size and speed are probably the most often cited reasons for using a compiler, in many situations they aren't the most important reasons. Some languages (such as C) are designed to allow pieces of a program to be compiled independently. These pieces are eventually combined into a final executable program by a program called the linker. This is called separate compilation. Separate compilation has many benefits. A program that, taken all at once, would exceed the limits of the compiler or the compiling environment can be compiled in pieces. Programs can be built and tested a piece at a time. Once a piece is working, it can be saved and forgotten. Collections of tested and working pieces can be combined into libraries for use by other programmers. As each piece is created, the complexity of the other pieces is hidden. All these features support the creation of large programs. Compiler debugging features have improved significantly. Early compilers only generated machine code, and the programmer inserted print statements to see what was going on. This is not always effective. Recent compilers can insert information about the source code into the executable program. This information is used by powerful source-level debuggers to show exactly what is happening in a program by tracing its progress through the source code. Some compilers tackle the compilation-speed problem by performing in-memory compilation. Most compilers work with files, reading and writing them in each step of the compilation process. In-memory compiler keep the program in RAM. For small programs, this can seem as responsive as an interpreter. The compilation process If you are going to create large programs, you need to understand the steps and tools in the compilation process. Some languages (C and C++, in particular) start compilation by running a preprocessor on the source code. The preprocessor is a simple program that replaces patterns in the source code with other patterns the programmer has defined (using preprocessor directives). Preprocessor directives are used to save typing and to increase the readability of the code (Later in the book, you'll learn how the design of C++ is meant to discourage much of the use of the preprocessor, since it can cause subtle bugs). The pre-processed code is written to an intermediate file. Compilers often do their work in two passes. The first pass parses the pre-processed code. The compiler breaks the source code into small units and organizes it into a structure called a tree. In the expression A + B the elements 'A', '+' and 'B' are leaves on the parse tree. The parser generates a second intermediate file containing the parse tree. A global optimizer is sometimes used between the first and second passes to produce smaller, faster code. In the second pass, the code generator walks through the parse tree and generates either assembly language code or machine code for the nodes of the tree. If the code generator creates assembly code, the assembler is run. The end result in both cases is an object module (a file with an extension of.o or .obj). A peephole optimizer is sometimes used in the second pass to look for pieces of code containing redundant assembly-language statements. The use of the word object to describe chunks of machine code is an unfortunate artifact. The word came into use before anyone thought of object-oriented programming. When a function in one object module makes a reference to a function or variable in another object module, the linker resolves these references. The linker brings in a special object module to perform start-up activities. The linker can also search through special files called libraries. A library contains a collection of object modules in a single file. A library is created and maintained by a program called a librarian. Static type checking The compiler performs type checking during the first pass. Type checking tests for the proper use of arguments in functions, and prevents many kinds of programming errors. Since type checking occurs during compilation rather than when the program is running, it is called static type checking. Some object-oriented languages (notably Smalltalk) perform all type checking at run-time (dynamic type checking). Dynamic type checking is less restrictive during development, since you can send any message to any object (the object figures out, at run time, whether the message is an error). It also adds overhead to program execution and leaves the program open for run-time errors that can only be detected through exhaustive testing. C++ uses static type checking because the language cannot assume any particular run-time support for bad messages. Static type checking notifies the programmer about misuse of types right away, and maximizes execution speed. As you learn C++ you will see that most of the language design decisions favor the same kind of high-speed, robust, production-oriented programming the C language is famous for. You can disable static type checking. You can also do your own dynamic type checking - you just need to write the code. Tools for separate compilation Separate compilation is particularly important when building large projects. In C and C++, a program can be created in small, manageable, independently tested pieces. To create a program with multiple files, functions in one file must access functions and data in other files. When compiling a file, the C or C++ compiler must know about the functions and data in the other files: their names and proper usage. The compiler insures the functions and data are used correctly. This process of telling the compiler the names of external functions and data and what they should look like is called declaration. Once you declare a function or variable, the compiler knows how to check to make sure it is used properly. At the end of the compilation process, the executable program is constructed from the object modules and libraries. The compiler produces object modules from the source code. Declarations vs. Before you can write your first program, you need to understand the proper way to write a declaration. Function declaration syntax A function declaration in Standard C and C++ gives the function name, the argument types passed to the function, and the return value of the function. The first keyword you see is the return value, all by itself: int. The arguments are enclosed in parentheses after the function name, in the order they are used. In C and C++, arguments in function declarations may have names. The compiler ignores the names but they can be helpful as mnemonic devices for the user. Function definitions Function definitions look like function declarations except they have bodies. A body is a collection of statements enclosed in braces. Braces denote the beginning and ending of a block of code; they have the same purpose as the begin and end keywords in Pascal. Since braces surround a statement or group of statements, you don't need a semicolon. Notice also that the arguments in the function definition must have names if you want to use the arguments in the function body (since they are never used here, they are optional). Function definitions are explored later in the book. Variable declaration syntax The meaning attributed to the phrase variable declaration has historically been confusing and contradictory, and it's important that you understand the correct definition so you can read code properly. A variable declaration tells the compiler what a variable looks like. It says I know you haven't seen this name before, but I promise it exists someplace, and it's a variable of X type. In a function declaration, you give a type (the return value), the function name, the argument list, and a semicolon. That's enough for the compiler to figure out that it's a declaration, and what the function should look like. By inference, a variable declaration might be a type followed by a name. For example: int A; could declare the variable A as an integer, using the above logic. Here's the conflict: there is enough information in the above code for the compiler to create space for an integer called A, and that's what happens. To resolve this dilemma, a keyword was necessary for C and C++ to say this is only a declaration; it's defined elsewhere. The keyword is extern. It can mean the definition is external to the file, or later in the file. Declaring a variable without defining it means using the extern keyword before a description of the variable, like this: extern int A; extern can also apply to function declarations. For func1( ), it looks like this: extern int func1(int length, int width); This statement is equivalent to the previous func1( ) declarations. Since there is no function body, the compiler must treat it as a function declaration rather than a function definition. The extern keyword is superfluous and optional for function declarations. Including headers Most libraries contain significant numbers of functions and variables. To declare the functions and external variables in the library, the user simply includes the header file. To include a header file, use the #include preprocessor directive. This tells the preprocessor to open the named header file and insert its contents where the #include statement appears. File names in double quotes, such as: #include local.h tell the preprocessor to search the current directory for the file and report an error if the file does not exist. File names in angle brackets tell the preprocessor to look through a search path specified in the environment. Setting the search path varies between machines, operating systems and C++ implementations. In C, a header file should not contain any function or data definitions because the header can be included in more than one file. At link time, the linker would then find multiple definitions and complain. In C++, there are two exceptions: inline functions and const constants (described later in the book) can both be safely placed in header files. New include format As C++ has evolved, different compiler vendors chose different extensions for file names. In addition, various operating systems have different restrictions on file names, in particular on name length. To smooth over these rough edges, the standard adopts a new format that allows file names longer than the notorious eight characters and eliminates the extension. Of course, you can also copy the headers given you by your compiler vendor to ones without extensions if you want to use this style before a vendor has provided support for it. The libraries that have been inherited from Standard C are still available with the.h extension. However, you can also use them in the C++ include style by prepending a c before the name. This provides a nice distinction to the reader indicating when you're using C versus C++ libraries. Linking The linker collects object modules (with file name extensions of.o or .obj), generated by the compiler, into an executable program the operating system can load and run. It is the last phase of the compilation process. Linker characteristics vary from system to system. Generally, you just tell the linker the names of the object modules and libraries you want linked together, and the name of the executable, and it goes to work. Some systems require you to invoke the linker yourself. With most C++ packages you invoke the linker through C++. In many situations, the linker is invoked for you, invisibly. Many linkers won't search object files and libraries more than once, and they search through the list you give them from left to right. This means that the order of object files and libraries can be important. If you have a mysterious problem that doesn't show up until link time, one possibility is the order in which the files are given to the linker. Using libraries Now that you know the basic terminology, you can understand how to use a library. To use a library: 1. Include the library's header file 2. Use the functions and variables in the library 3. Link the library into the executable program These steps also apply when the object modules aren't combined into a library. If it has not already encountered the definition for the function or variable, it adds it to its list of unresolved references. If the linker has already encountered the definition, the reference is resolved. If the linker cannot find the definition in the list of object modules, it searches the libraries. Libraries have some sort of indexing so the linker doesn't need to look through all the object modules in the library -- it just looks in the index. When the linker finds a definition in a library, the entire object module, not just the function definition, is linked into the executable program. Note that the whole library isn't linked, just the object module in the library that contains the definition you want (otherwise programs would be unnecessarily large). If you want to minimize executable program size, you might consider putting a single function in each source code file when you build your own libraries. This requires more editing, but it can be helpful to the user. Because the linker searches files in the order you give them, you can pre-empt the use of a library function by inserting a file with your own function, using the same function name, into the list before the library name appears. Since the linker will resolve any references to this function by using your function before it searches the library, your function is used instead of the library function. Secret additions When a C or C++ executable program is created, certain items are secretly linked in. One of these is the startup module, which contains initialization routines that must be run any time a C or C++ program executes. These routines set up the stack and initialize certain variables in the program. The linker always searches the standard library for the compiled versions of any standard functions called in the program. The iostream functions, for example, are in the standard C++ library. Because the standard library is always searched, you can use any function (or class, in C++) in the library by simply including the appropriate header file in your program. To use the iostream functions, you just include the iostream.h header file. In non-standard implementations of C (and C++ C-code generators that use non-standard implementations of C), commonly used functions are not always contained in the library that is searched by default. Math functions, in particular, are often kept in a separate library. You must explicitly add the library name to the list of files handed to the linker. Using plain C libraries Just because you are writing code in C++, you are not prevented from using C library functions. There has been a tremendous amount of work done for you in these functions, so they can save you a lot of time. This book will use C library functions when convenient (Standard C library functions will be used to increase the portability of the programs). Using pre-defined C library functions is quite simple: just include the appropriate header file and use the function. The program will use the pre-defined C++ iostream classes that comes with all C++ packages. The iostreams class handles input and output for files, with the console, and with standard input and output (which may be redirected to files or devices). In this very simple program, a stream object will be used to print a message on the screen. The iostream package automatically defines a variable (an object) called cout that accepts all data bound for standard output. C++ allows operators to be overloaded. When you overload an operator, you give it a new meaning when that operator is used with an object of a particular type. Chapter XX covers operator overloading in detail. When the program starts, it executes initialization code and calls a special function, main( ). You put the primary code for the program here. There can be many sets of braces within a function definition, but there must always be at least one set surrounding the function body. Since main( ) is a function, it must follow these rules. Unless you intend to return a value from your program (some operating systems can utilize a return value from a program), main( ) should have a return type of void, so the compiler won't issue a warning message. C and C++ are free form languages. With few exceptions, the compiler ignores carriage returns and white space, so it must have some way to determine the end of a statement. With iostreams, you can string together a series of arguments like this, which makes the class easy to use. Text inside double quotes is called a string. The compiler creates space for strings and stores the ASCII equivalent for each character in this space. The string is terminated with a value of 0 to indicate the end of the string. The special iostream function endl outputs the line and a newline. Inside a character string, you can insert special characters that do not print using escape sequences. These consist of a backslash (\) followed by a special code. For example \n means new line. Your compiler manual or local Standard C guide gives a complete set of escape sequences; others include \t (tab), \\ (backslash) and \b (backspace). Notice that the entire phrase terminates with a semicolon. String arguments and constant numbers are mixed in the cout statement. For simple, one-file programs like this one, most compilers will take you all the way through the process. For example, to use the Gnu C++ compiler (which is freely available), you say: g++ Hello.cpp Other compilers will have a similar syntax; consult your compiler's documenation for details. More about iostreams So far you have seen only the most rudimentary aspect of the iostreams class. The output formatting available with iostreams includes number formatting in decimal, octal and hex. Floating-point numbers are determined automatically, by the compiler. In addition, any character can be sent to a stream object using a cast to a character (a char is a data type designed to hold characters), which looks like a function call: char( ), along with the character's ASCII value. In the above program, an escape is sent to cout. String concatenation An important feature of the Standard C preprocessor is string concatenation. This feature is used in some of the C++ examples in this book. If two quoted strings are adjacent, and no punctuation is between them, the compiler will paste the strings together as a single string. The object used for standard input is cin. An example of redirection is shown later in this chapter. For example, if you give it an integer argument, it waits for an integer from the console. Since the extern keyword isn't used, the compiler creates space for number at that point. If a program takes input from standard input (cin for iostreams) and sends its output to standard output (cout for iostreams), that input and output can be redirected. Input can be taken from a file, and output can be sent to a file. As a useful example, suppose you want to record the number of times you perform an activity, but the program that records the incidents must be loaded and run many times, and the machine may be turned off, etc. To keep a permanent record of the incidents, you must store the data in a file. This file will be called INCIDENT.DAT and will initially contain the character 0. For easy reading, it will always contain ASCII digits representing the number of incidents. The program should print a number one larger than the one you type. The command executes and control returns to the program. Although it works fine here, reading and writing the same file isn't always a safe thing to do -- if you aren't careful you can end up with garbage in the file. This program shows you how easy it is to use plain C library functions in C++: just include the header file and call the function. The upward compatibility from C to C++ is a big advantage if you are learning the language starting from a background in C. Although it works fine here, reading and writing the same file isn't always a safe thing to do -- if you aren't careful you can end up with garbage in the file. This program shows you how easy it is to use plain C library functions in C++: just include the header file and call the function. The upward compatibility from C to C++ is a big advantage if you are learning the language starting from a background in C. Summary Exercises 3: The C in C++ The user-defined data type, or class, is what distinguishes C++ from traditional procedural languages. A class is a new data type that you or someone else creates to solve a particular type of problem. Once a class is created, anyone can use it without knowing the specifics of how it works, or even how classes are built. This chapter will teach you enough of the basics of C and C++ so you can utilize a class that someone else has written. The quick coverage of C++ features which are similar to C features will continue in chapters 3 and 4. This chapter treats classes as if they are just another built-in data type available for use in programs. So you don't see any undefined concepts, the process of writing your own classes must be delayed until the following chapter. This may cause a tedious delay for experienced C programmers. However, to leap past the necessary basics would hopelessly confuse programmers attempting to move to C++ from other languages. C++ uses all C's execution control statements. These include if-else, while, do-while, for, and a selection statement called switch. C++ also allows the infamous goto, which will be avoided in this book. True and false in C An expression is true if it produces a non-zero integral value. An expression is false if it produces an integral zero. All conditional statements use the truth or falsehood of a conditional expression to determine the execution path. An example of a conditional expression is A == B. This uses the conditional operator == to see if the variable A is equivalent to the variable B. The expression returns 1 if the statement is true and 0 if it is false. The next chapter covers conditional statements. The two forms are: if(expression) statement or if(expression) statement else statement The expression evaluates to true or false. The statement means either a simple statement terminated by a semicolon or compound statement, which is a group of simple statements enclosed in braces. Any time the word statement is used, it is always implied that the statement can be simple or compound. Note this statement can also be another if, so they can be strung together. Pascal programmers should notice that the then is implied in C and C++, which are terse languages. Then isn't essential, so it was left out. Since C and C++ are free form languages, the extra spaces, tabs and carriage returns do not affect the resulting program. It is conventional to indent the body of a control flow statement so the reader may easily determine where it begins and ends26. A statement repeats until the controlling expression evaluates to false. The form for a while loop is while(expression) statement The expression is evaluated once at the beginning of the loop, and again before each further iteration of the statement. This example stays in the body of the while loop until you type the secret number or press control-C. In a simple while, if the conditional is false the first time the statement never executes. The initialization code executes once at the very beginning. The expression is tested before each iteration (if it evaluates to false at the beginning, the statement never executes). At the end of each loop, the step executes. Declaring all variables at the beginning of the block requires the programmer to write in a particular way because of the implementation details of the language. Most people don't know all the variables they are going to use before they write the code, so they must keep jumping back to the beginning of the block to insert new variables, which is awkward and causes errors. It is confusing to read the code because each block starts with a clump of variable declarations, and the variables might not be used until much later in the block. In C++ (not in C) you can spread your variable declarations throughout the block. Whenever you need a new variable, you can define it right where you use it. In addition, you can initialize the variable at the point you declare it, which prevents a certain class of errors. Defining variables at any point in a scope allows a more natural coding style and makes code easier to understand. C++ compilers collect all the variable declarations in the block and secretly place them at the beginning of the block. The break and continue Keywords Inside the body of any of the looping constructs you can control the flow of the loop using break and continue. After each of the sub-menu selections, the continue keyword is used to pop back up to the beginning of the while loop. The switch compares the result of selector to each integral-value. If it finds a match, the corresponding statement (simple or compound) executes. If no match occurs, the default statement executes. You will notice in the above definition that each case ends with a break, which causes execution to jump to the end of the switch body. This is the conventional way to build a switch statement, but the break is optional. If it is missing, the code for the following case statements execute until a break is encountered. Although you don't usually want this kind of behavior, it can be useful to an experienced C programmer. The switch statement is a very clean way to implement multi-way selection (i.e., selecting from among a number of different execution paths), but it requires a selector that evaluates to an integral value at compile-time. If you want to use, for example, a string as a selector, it won't work in a switch statement. For a string selector, you must use instead a series of if statements and compare the string inside the conditional. The next time the selector is evaluated, quit == 0 returns false so the body of the while does not execute. Introduction to C and C++ operators You can think of operators as a special type of function (C++ operator overloading treats operators precisely that way). An operator takes one or more arguments and produces a new value. The arguments are in a different form than ordinary function calls, but the effect is the same. You should be reasonably comfortable with the operators used so far from your previous programming experience. The full set of operators are enumerated in the next chapter. Precedence Operator precedence defines the order in which an expression evaluates when several different operators are present. C and C++ have specific rules to determine the order of evaluation. The easiest to remember is that multiplication and division happen before addition and subtraction. After that, if an expression isn't transparent to you it probably won't be for anyone reading the code, so you should use parentheses to make the order of evaluation explicit. Shortcuts can make code much easier to type, and sometimes much harder to read. Perhaps the designers thought it would be easier to understand a tricky piece of code if your eyes didn't have to scan as large an area of print. One of the nicer shortcuts is the auto-increment and auto-decrement operators. You often use these to change loop variables, which control the number of times a loop executes. The auto-decrement operator is -- and means decrease by one unit. The auto-increment operator is ++ and means increase by one unit. If A is an int, for example, the expression ++A is equivalent to (A = A + 1). Auto-increment and auto-decrement operators produce the value of the variable as a result. If the operator appears before the variable, (i.e., ++A), the operation is performed and the value is produced. If the operator appears after the variable (i.e. A++), the value is produced, then the operation is performed. Often, however, it is easiest to read from cin and write to cout. The program can be tested by typing at the console, and when it is working, files can be manipulated via redirection on the command line (in Unix and MS-DOS). Simple cat program So far, all the messages you've seen are sent via operator overloading to stream objects. In C++, a message is usually sent to an object by calling a member function for that object. A member function looks like a regular function -- it has a name, argument list and return value. However, it must always be connected to an object. It can never be called by itself. A member function is always selected for a particular object via the dot (.) member selection operator. The iostream class has several non-operator member functions. One of these is get( ), which can be used to fetch a single character (or a string, if it is called differently). The following program uses get( ) to read characters from the cin object. The program uses the complementary member function put( ) to send characters the cout object. Characters are read from standard input and written to standard output. According to plain C syntax, the character variable c looks like it is passed by value to the member function get( ). Yet c is used in the put( ) member function as if get( ) had modified the value of c, which is impossible if it was passed by value! What goes on here? C++ has added another kind of argument passing: pass-by-reference. If a function argument is defined as pass-by-reference, the compiler takes the address of the variable when the function is called. The argument of the stream function get( ) is defined as pass-by-reference, so the above program works correctly. Chapter 4 describes passing by reference in more detail. The first part of that chapter describes addresses, which you must understand before references make any sense. Handling spaces in input To read and use more than a character at a time from standard input, you will need to use a buffer. A buffer is a data-storage area used to hold and manipulate a group of data items with identical types. In C and C++, you can create a buffer to hold text with an array of characters. To define an array, give the data type, a name for the array, and the size in brackets. The idea is the same in each case: you want to get some input. You need different kinds of input, but you don't have to worry about it because the language takes care of the differentiation for you. You must type a carriage return before any of the input is read. To read and manipulate anything more than a simple character or word using iostreams, it is best to use the get( ) function. When used with a character buffer, get( ) needs to know the maximum number of characters it should read (usually the size of the buffer) and optionally the terminating character it should look for before it stops reading input. This terminating character that get( ) looks for (the delimiter) defaults to a new line (\n). You don't need to change the delimiter if you just want to read the input a line at a time. To change the delimiter, add the character you wish to be the delimiter in single quotes as the third argument in the list. When get( ) matches the delimiter with the terminating character, the terminating character isn't copied into the character buffer; it stays on the input stream. This means you must read the terminating character and throw it away, otherwise the next time you try to fill your character buffer using get( ), the function will immediately read the terminating character and stop. The character trash is only used for throwing away the line terminator. Because the new line was never put in buf, you must send a new line out when you print the line. The return value of cin.get( ) for lines is the same as the overloaded version of the same function for single characters. It is true as long as it read some input (so the body of the loop is executed) and false when the end of the input is reached. Try redirecting the contents of a text file into GETLINE. Aside: examining header files As your knowledge of C++ increases, you will find that the best way to discover the capabilities of the iostreams class, or any class, is to look at the header file where the class is defined. The header file will contain the class declaration. You won't completely understand the class declaration until you've read the next chapter. The class declaration contains some private and protected elements, which you don't have access to, and a list of public elements, usually functions, that you as the user of the class may utilize. On Unix, you must ask your system administrator where the C++ INCLUDE files are located. This section contains examples of useful utilities. It uses two functions from the Standard C library, both of which are declared in the header file string.h. strlen( ) finds the length of a string, not including the zero byte that terminates all strings. For this program, a word is separated by white space, which is a space or a tab. The first time you call strtok( ), you hand it the character buffer, and all the subsequent times you hand it a zero, which tells it to use the same buffer it used for the last call (moving ahead each time strtok( ) is called). When it can't find any more words in the line, strtok( ) returns zero. Since the value produced by auto-incrementing the variable is ignored, it doesn't matter whether you put the increment first or last. To count words, strtok( ) is set up for the first call by handing it the text buffer buf. If it finds a word, the word is counted. If there are more words, they are counted. The keyword const is used to prevent maxwidth from being changed. It has two purposes: the compiler will generate an error message if you ever try to change the value, and an optimizer can use the fact that a variable is const to create better code. It is always a good idea to make a variable const if you know it should never change. Notice the way buf, c, and trash are all declared with a single char statement. You can declare all types of data this way, just by separating the variable names with commas. IOstream support for file manipulation All the examples in this chapter have used IO redirection to handle input and output. Although this approach works fine, iostreams have a much faster and safer way to read and write files. This is accomplished by including fstream.h instead of (or in addtion to) iostream.h, then creating and using fstream objects in almost the identical fashion you use ordinary iostream objects. The second line checks to see if the file was successfully opened, using a function in require.h that will be described later in the book. The third line creates an ofstream operator that is just like an ifstream except it writes to a file. This is also checked for successful opening. The while loop simply gets characters from infile with the member function get( ), and puts them into outfile with put( ), until the get( ) returns false (that is, zero). The files are automatically closed when the objects are destroyed, which is another benefit of using fstreams for manipulating files -- you don't have to remember to close the files. There's also a set of iostream classes for doing in-memory formatting, in the header file strstream.h. Introduction to C++ data Data types can be built-in or abstract. A built-in data type is one that the compiler intrinsically understands, one that comes with the compiler. The types of built-in data are identical in C and C++. A user-defined data type is one you or another programmer create as a class. These are commonly referred to as abstract data types. The compiler knows how to handle built-in types when it starts up; it learns how to handle abstract data types by reading header files containing class declarations. Basic built-in types The Standard C specification doesn't say how many bits each of the built-in types must contain. Instead, it stipulates the minimum and maximum values the built-in type must be able to hold. When a machine is based on binary, this maximum value is directly translated into bits. If a machine uses, for instance, binary-coded decimal (BCD) to represent numbers then the amount of space in the machine required to hold the maximum numbers for each data type will change. The minimum and maximum values that can be stored in the various data types are defined in the system header files LIMITS.H and FLOAT.H C and C++ have four basic built-in data types, described here for binary-based machines. A char is for character storage and uses a minimum of one byte of storage. An int stores an integral number and uses a minimum of two bytes of storage. The float and double types store floating-point numbers, often in IEEE floating-point format. You can define and initialize variables at the same time. If you don't initialize a variable, its contents are undefined (although some compilers will initialize to 0). The second part of the program defines and initializes variables at the same time. Notice the use of exponential notation in the constant 6e-4, meaning: 6 times 10 to the minus fourth power. bool, true, and false Virtually everyone uses Booleans, and everyone defines them differently. 27 Some use enumerations, others use typedefs. A typedef is a particular problem because you can't overload on it (a typedef to an int is still an int) or instantiate a unique template with it. A class could have been created for bool in the standard library, but this doesn't work very well either, because you can only have one automatic type conversion operator from a class without causing overload resolution problems. The best approach for such a useful type is to build it into the language. A bool type can have two states expressed by the built-in constants true (which converts to an integral one) and false (which converts to an integral zero). All three names are keywords. In addition, some language elements have been adapted: Element Usage with bool andand ||! Take bool arguments and return bool. Ideally, the compiler will give you a warning as a suggestion to correct the situation. An idiom that falls under poor programming style is the use of ++ to set a flag to true. This is still allowed, but deprecated, which means that at some time in the future it will be made illegal. Pointers will also be automatically converted to bool when necessary. Specifiers Specifiers modify the meanings of the basic built-in types, and expand the built-in types to a much larger set. There are four specifiers: long, short, signed and unsigned. Long and short modify the maximum and minimum values a data type will hold. A plain int must be at least the size of a short. The size hierarchy for integral types is: short int, int, long int. On a machine with a 64-bit word, for instance, all the data types might be 64 bits. The size hierarchy for floating point numbers is: float, double, and long double. Long float is not allowed in Standard C. There are no short floating-point numbers. The signed and unsigned specifiers tell the compiler how to use the sign bit with integral types and characters (floating-point numbers always contain a sign). An unsigned number does not keep track of the sign and can store positive numbers twice as large as the positive numbers that can be stored in a signed number. Signed is the default and is only necessary with char; char may or may not default to signed. By specifying signed char, you force the sign bit to be used. Scoping Scoping rules tell you where a variable is valid, where it is created and where it gets destroyed (i.e., goes out of scope). The scope of a variable extends from the point where it is defined to the first closing brace matching the closest opening brace before the variable is declared. A variable can only be used when inside its scope. Scopes can be nested, indicated by matched pairs of braces inside other matched pairs of braces. Nesting means you can access a variable in a scope that encloses the scope you are in. In the above example, the variable scp1 is available inside all of the other scopes, while scp3 is only available in the innermost scope. Defining data on the fly There is a significant difference between C and C++ when defining variables. Both languages require that variables be defined before they are used, but C requires all the variable definitions at the beginning of a scope. While reading C code, a block of variable definitions is often the first thing you see when entering a scope. These variable definitions don't usually mean much to the reader because they appear apart from the context in which they are used. C++ allows you to define variables anywhere in the scope, so you can define a variable right before you use it. This makes the code much easier to write and reduces the errors you get from being forced to jump back and forth within a scope. It makes the code easier to understand because you see the variable definition in the context of its use. This is especially important when you are defining and initializing a variable at the same time ÄÄ you can see the meaning of the initialization value by the way the variable is used. The p in the outer scope is in the same situation. The definition of i in the for loop is rather tricky. You might think that i is only valid within the scope bounded by the opening brace that appears after the for. The variable i is actually valid from the point where it is declared to the end of the scope that encloses the for loop. This is consistent with C, where the variable i must be declared at the beginning of the scope enclosing the for if it is to be used by the for. Specifying storage allocation When creating data, you have a number of options to specify the lifetime of the data, how the data is allocated, and how the data is treated by the compiler. Global variables Global variables are defined outside all function bodies and are available to all parts of the program (even code in other files). Global variables are unaffected by scopes and are always available (i.e., the lifetime of a global variable lasts until the program ends). If the existence of a global variable in one file is declared using the extern keyword in another file, the data is available for use by the second file. They are often called automatic variables because they automatically come into being when the scope is entered, and go away when the scope closes. The keyword auto makes this explicit, but local variables default to auto so it is never necessary to declare something as an auto. Register variables A register variable is a type of local variable. The register keyword tells the compiler make accesses to this variable as fast as possible. Increasing the access speed is implementation dependent but, as the name suggests, it is often done by placing the variable in a register. There is no guarantee that the variable will be placed in a register or even that the access speed will increase. It is a hint to the compiler. There are restrictions to the use of register variables. You cannot take or compute the address of a register variable. A register variable can only be declared within a block (you cannot have global or static register variables). You can use a register variable as a formal argument in a function (i.e., in the argument list). Normally, variables defined local to a function disappear at the end of the function scope. When you call the function again, storage for the variables is created anew and the data is re-initialized. If you want the data to be extant throughout the life of the program, you can define that variable to be static and give it an initial value. The initialization is only performed when the program begins to execute, and the data retains its value between function calls. This way, a function can remember some piece of information between function calls. You may wonder why global data isn't used instead. The beauty of static data is that it is unavailable outside the scope of the function, so it can't be inadvertently changed. This localizes errors. If the keyword static is not used, the value printed will always be '1'. The second meaning of static is related to the first in the unavailable outside a certain scope sense. When static is applied to a function name or to a variable that is outside of all functions, it means this name is unavailable outside of this file. The function name or variable is local to the file or has file scope. This definition will be delayed until after classes have been described later in the chapter. It tells the compiler that a piece of data or a function exists, even if the compiler hasn't yet seen it in the file currently being compiled. This piece of data or function may exist in some other file or further on in the current file. This definition can be in the current file, later on, or in a separate file. When the compiler reaches the definition of i, no other declaration is visible so it knows it has found the same i declared earlier in the file. If you were to define i as static, you would be telling the compiler that i is defined globally (via the extern), but it also has file scope (via the static), so the compiler will generate an error. Linkage To understand the behavior of C and C++ programs, you need to know about linkage. Linkage describes the storage created in memory to represent an identifier as it is seen by the linker. An identifier is represented by storage in memory to hold a variable or a compiled function body. There are two types of linkage: internal linkage and external linkage. Internal linkage means that storage is created to represent the identifier for the file being compiled only. Other files may use the same identifier with internal linkage or for a global variable, and no conflicts will be found by the linker ÄÄ separate storage is created for each identifier. Internal linkage is specified by the keyword static in C and C++. External linkage means that a single piece of storage is created to represent the identifier for all files being compiled. The storage is created once, and the linker must resolve all other references to that storage. Global variables and function names have external linkage. These are accessed from other files by declaring them with the keyword extern. Variables defined outside all functions (with the exception of const in C++) and function definitions default to external linkage. You can specifically force them to have internal linkage using the static keyword. You can explicitly state that an identifier has external linkage by defining it with the extern keyword. Defining a variable or function with extern is not necessary in C, but it is sometimes necessary for const in C++. Automatic (local) variables exist only temporarily, on the stack, while a function is being called. The linker doesn't know about automatic variables, and they have no linkage. Constants In old (pre-Standard) C, if you wanted to make a constant, you had to use the preprocessor: #define PI 3.14159 Everywhere you used PI, the value was substituted by the preprocessor (you can still use this method in C and C++). When you use the preprocessor to create constants, you place control of those constants outside the scope of the compiler. No type checking is performed on the name PI and you can't take the address of PI (so you can't pass a pointer or a reference to PI). PI cannot be a variable of a user-defined type. The meaning of PI lasts from the point it is defined to the end of the file; the preprocessor doesn't recognize scoping. C++ introduces the concept of a named constant that is just like a variable, except its value cannot be changed. The modifier const tells the compiler that a name represents a constant. Any data type, built-in or user-defined, may be defined as const. If you define something as const and then attempt to modify it, the compiler will generate an error. You cannot use the const modifier alone (at one time, it defaulted to int when used by itself). You must specify the type, like this: const int x = 10; In Standard C and C++, you can use a named constant in an argument list, even if the argument it fills is a pointer or a reference (i.e., you can take the address of a const). A const has a scope, just like a regular variable, so you can hide a const inside a function and be sure that the name will not affect the rest of the program. The const was taken from C++ and incorporated into Standard C, albeit quite differently. In C++, const is meant to go into header files, and to be used in places where you would normally use a #define name. In Standard C, a const cannot be used where the compiler is expecting a constant expression. A const must have an initializer in C++. Standard C doesn't require an initializer; if none is given it initializes the const to 0. In C++, a const doesn't necessarily create storage. In Standard C a const always creates storage. Whether or not storage is reserved for a const in C++ depends on how it is used. In general, if a const is used simply to replace a name with a value (just as you would use a #define), then storage doesn't have to be created for the const. If no storage is created (this depends on the complexity of the data type and the sophistication of the compiler), the values may be folded into the code for greater efficiency after type checking, not before, as with #define. If, however, you take an address of a const (even unknowingly, by passing it to a function that takes a reference argument) or you define it as extern, then storage is created for the const. In C++, a const that is outside all functions has file scope (i.e., it is invisible outside the file). That is, it defaults to internal linkage. This is very different from all other identifiers in C++ (and from const in Standard C!) that default to external linkage. Because const has implied file scope, you can put it in header files (in C++ only) with no conflicts at link time. Since a const in C++ defaults to internal linkage, you can't just define a const in one file and reference it as an extern in another file. The initialization establishes this as a definition, not a declaration. The declaration: extern const x; in C++ means that the definition exists elsewhere (again, this is not necessarily true in Standard C). You can now see why C++ requires a const definition to have an initializer: the initializer distinguishes a declaration from a definition (in Standard C it's always a definition, so no initializer is necessary). With an external const declaration, the compiler cannot do constant folding because it doesn't know the value. Constant values In C++, a const must always have an initialization value (in Standard C, this is not true). Constant values for built-in types are expressed as decimal, octal, hexadecimal, or floating-point numbers (sadly, binary numbers were not considered important), or as characters. In the absence of any other clues, the compiler assumes a constant value is a decimal number. The numbers 47, 0 and 1101 are all treated as decimal numbers. A constant value with a leading 0 is treated as an octal number (base 8). Base 8 numbers can only contain digits 0-7; the compiler flags other digits as an error. A legitimate octal number is 017 (15 in base 10). A constant value with a leading 0x is treated as a hexadecimal number (base 16). Base 16 numbers contain the digits 0-9 and a-f or A-F. A legitimate hexadecimal number is 0x1fe (510 in base 10). Floating point numbers can contain decimal points and exponential powers (represented by e, which means 10 to the power). Both the decimal point and the e are optional. If you assign a constant to a floating-point variable, the compiler will take the constant value and convert it to a floating-point number (this process is called implicit type conversion). However, it is a good idea to use either a decimal point or an e to remind the reader you are using a floating-point number; some older compilers also need the hint. Legitimate floating-point constant values are: 1e4, 1.0001, 47.0, 0.0 and -1.159e-77. You can add suffixes to force the type of floating-point number: f or F forces a float, L or l forces a long double, otherwise the number will be a double. Character constants are characters surrounded by single quotes, as: 'A', '0', ' '. Notice there is a big difference between the character '0' (ASCII 96) and the value 0. Special characters are represented with the backslash escape: '\n' (new-line), '\t' (tab), '\\' (backslash), '\r' (carriage return), '\' (double quote), '\'' (single quotes), etc. Use this keyword when you read some value outside the control of the system, such as a register in a piece of communication hardware. A volatile variable is always read whenever its value is required, even if it was just read the line before. Operators and their use Operators were briefly introduced in chapter 2. This section covers all the operators in C and C++. All operators produce a value from their operands. This value is produced without modifying the operands, except with assignment, increment and decrement operators. Modifying an operand is called a side effect. The most common use for operators that modify their operands is to generate the side effect, but you should keep in mind that the value produced is available for your use just as in operators without side effects. Assignment Assignment is performed with the operator =. It means take the right-hand side (often called the rvalue) and copy it into the left-hand side (often called the lvalue). An rvalue is any constant, variable, or expression that can produce a value, but an lvalue must be a distinct, named variable (that is, there must be a physical space to store a value). For instance, you can assign a constant value to a variable (A = 4;), but you cannot assign anything to constant value - it cannot be an lvalue (you can't say 4 = A;). Integer division truncates the result (it doesn't round). The modulus operator cannot be used with floating-point numbers. This is denoted by an operator followed by an equal sign, and is consistent with all the operators in the language (whenever it makes sense). Introduction to preprocessor macros Notice the use of the macro print( ) to save typing (and typing errors!). The arguments in the parenthesized list following the macro name are substituted in all the code following the closing parenthesis. Operators are just a different kind of function call There are two differences between the use of an operator and an ordinary function call. The syntax is different; an operator is often called by placing it between or sometimes after the arguments. The second difference is that the compiler determines what function to call. If you use operator + with a floating-point number and an integer, the compiler calls a special function to turn the int into a float, and then calls the floating-point addition code. It is important to be aware that operators are simply a different kind of function call. In C++ you can define your own functions for the compiler to call when it encounters operators used with your abstract data types. This feature is called operator overloading and is described in chapter 5. Relational operators Relational operators establish a relationship between the values of the operands. They produce a value of 1 if the relationship is true, and a value of 0 if the relationship is false. Remember that in C and C++, a statement is true if it has a non-zero value, and false if it has a value of zero. Bitwise operators The bitwise operators allow you to manipulate individual bits in a number (thus they only work with integral numbers). Bitwise operators perform boolean algebra on the corresponding bits in the two arguments to produce the result. The bitwise AND operator (and) produces a one in the output bit if both input bits are one; otherwise it produces a zero. The bitwise OR operator (|) produces a one in the output bit if either input bit is a one and only produces a zero if both input bits are zero. The bitwise, EXCLUSIVE OR, or XOR (^) produces a one in the output bit if one or the other input bit is a one, but not both. The bitwise NOT (~, also called the ones complement operator) is a unary operator - it only takes one argument (all other bitwise operators are binary operators). Bitwise NOT produces the opposite of the input bit - a one if the input bit is zero, a zero if the input bit is one. Bitwise operators can be combined with the = sign to unite the operation and assignment: and=, |= and ^= are all legitimate (since ~ is a unary operator it cannot be combined with the = sign). Shift operators The shift operators also manipulate bits. If the value after the shift operator is greater than the number of bits in the left-hand operand, the result is undefined. If the left-hand operand is unsigned, the right shift is a logical shift so the upper bits will be filled with zeros. If the left-hand operand is signed, the right shift may or may not be a logical shift. The lvalue is replaced by the lvalue shifted by the rvalue. Notice the definitions (or at least declarations) of ROL( ) and ROR( ) must be seen by the compiler in BITWISE.CPP before the functions are used. The bitwise functions are generally extremely efficient to use because they translate directly into assembly language statements. Sometimes a single C or C++ statement will generate a single line of assembly code. Unary operators Bitwise NOT isn't the only operator that takes a single argument. Its companion, the logical NOT (!), will take a true value (nonzero) and produce a false value (zero). The unary minus (-) and unary plus (+) are the same operators as binary minus and plus - the compiler figures out which usage is intended by the way you write the expression. For instance, the statement x = -a; has an obvious meaning. The compiler can figure out: x = a * -b; but the reader might get confused, so it is safer to say: x = a * (-b); The unary minus produces the negative of the value. Unary plus provides symmetry with unary minus, although it doesn't do much. The increment and decrement operators (++ and --) were introduced in chapter 2. These are the only operators other than those involving assignment that have side effects. The increment operator increases the variable by one unit (unit can have different meanings according to the data type - see the chapter on pointers) and the decrement operator decreases the variable by one unit. The value produced depends on whether the operator is used as a prefix or postfix operator (before or after the variable). Used as a prefix, the operator changes the variable and produces the changed value. As a postfix, the operator produces the unchanged value and then the variable is modified. The last unary operators are the address-of (and), dereference (*) and cast operators in C and C++, and new and delete in C++. Address-of and dereference are used with pointers, which will be described in Chapter 4. Casting is described later in this chapter, and new and delete are described in Chapter 6. Conditional operator or ternary operator This operator is unusual because it has 3 operands. It is truly an operator because it produces a value, unlike the ordinary if-else statement. It consists of three expressions: if the first expression (followed by a?) evaluates to true, the expression following the ? If the first expression is false, the third expression (following a :) is executed and its result becomes the value produced by the operator. The conditional operator can be used for its side effects or for the value it produces. Here's a code fragment that demonstrates both: A = --B? B : (B = -99); Here, the conditional produces the rvalue. A is assigned to the value of B if the result of decrementing B is nonzero. If B became zero, A and B are both assigned to -99. B is always decremented, but it is only assigned to -99 if the decrement causes B to become 0. A similar statement can be used without the A = just for its side effects: --B? B : (B = -99); Here the second B is superfluous, since the value produced by the operator is unused. An expression is required between the? The comma operator The comma is not restricted to separating variable names in multiple definitions (i.e.: int i, j, k;). When used as an operator to separate expressions, it produces only the value of the last expression. All the rest of the expressions in the comma-separated list are only evaluated for their side effects. This code fragment increments a list of variables and uses the last one as the rvalue: A = (B++,C++,D++,E++); The parentheses are critical here. The variable a is assigned to the value of b, and the value of b is also produced by the operator =. Generally you want to use the equivalence operator == inside a conditional statement, not assignment. This one bites a lot of programmers. A similar problem is using bitwise AND and OR instead of logical. Bitwise AND and OR use one of the characters (and or |) while logical AND and OR use two (andand and ||). Just as with = and ==, it's easy to just type one character instead of two. Casting operators The word Cast in C is used in the sense of casting into a mold. C will automatically change one type of data into another if it makes sense to the compiler. For instance, if you assign an integral value to a floating-point variable, the compiler will secretly call a function (or more probably, insert code) to convert the int to a float. Casting allows you to make this type conversion explicit, or to force it when it wouldn't normally happen. To perform a cast, put the desired data type (including all modifiers) inside parentheses to the left of the value. This value can be a variable, constant, the value produced by an expression or the return value of a function. Here's an example: int B = 200; A = (unsigned long int)B; You can even define casting operators for user-defined data types. This usually occurs when casting pointers, not when making simple casts like the one shown above. C++ has an additional kind of casting syntax, which follows the function-call syntax used with constructors (defined later in this chapter). As described earlier in this chapter, sizeof( ) tells you the number of bytes used by any particular variable. It can also give the size of a data type (with no variable name): printf(sizeof(double) = %d\n, sizeof(double)); sizeof( ) can also give you the sizes of user-defined data types. This is used later in the book. The asm keyword This is an escape mechanism that allows you to write assembly code for your hardware within a C++ program. The exact syntax of the assembly language is compiler-dependent and can be discovered in your compiler's documentation. Explicit operators These are keywords for bitwise and logical operators. Non-U.S. programmers without keyboard characters like and, |, ^, and so on, were forced to use C's horrible trigraphs, which were not only annoying to type, but obscure when reading. In C++, a subprogram is called a function. All functions have return values (although that value can be nothing) so functions in C++ are very similar to functions in Pascal. In old (pre-Standard) C, you could call a function with any number or type of arguments, and the compiler wouldn't complain. Everything seemed fine until you ran the program. You got mysterious results (or worse, the program crashed) with no hints as to why. The lack of help with argument passing and the enigmatic bugs that resulted is probably one reason why C was dubbed a high-level assembly language. Pre-Standard C programmers just adapted to it. With function prototyping, you always use a prototype when declaring and defining a function. When the function is called, the compiler uses the prototype to insure the proper arguments are passed in, and that the return value is treated correctly. If the programmer makes a mistake when calling the function, the compiler catches the mistake. The order and type of the arguments must match in the declaration, definition and function call. You must indicate the type of each argument. In a function declaration, the following form is also acceptable: int translate(float, float, float); since the compiler doesn't do anything but check for types when the function is called. Since it is unnamed, you cannot use it in the function body, of course. This option of ignoring an argument in the list is possible if you leave the name in, but you will get an obnoxious warning message about the value being unused every time you compile the function. The warning is eliminated if you remove the name. Standard C and C++ have two other ways to declare an argument list. If you have an empty argument list you can declare it as foo( ) in C++, which tells the compiler there are exactly zero arguments. Remember this only means an empty argument list in C++. In Standard C it means an indeterminate number of arguments (which is a hole in Standard C since it disables type checking in that case). In both Standard C and C++, the declaration foo(void); means an empty argument list. The void keyword means nothing in this case (it can also mean no type when applied to certain variables). The other option for argument lists occurs when you don't know how many arguments or what type of arguments you will have; this is called a variable argument list. This uncertain argument list is represented by ellipses (...). Defining a variable argument list is significantly more complicated than a plain function. You can use a variable argument list declaration for a function that has a fixed set of arguments if (for some reason) you want to disable the error checks of function prototyping. Handling variable argument lists is described in the library section of your local Standard C guide. Function return values A function prototype may also specify the return value of a function. The type of this value precedes the function name. If no type is given, the return value type defaults to int (most things in C default to int). If you want to specify that no value is returned, as in a Pascal procedure, the void keyword is used. This will generate an error if you try to return a value from the function. This is done with the return statement. If return has an argument, it becomes the return value of the function. The else is unnecessary because the first if that evaluates true causes an exit of the function via the return statement. Notice that a function declaration is not necessary because the function definition appears before it is used in main( ), so the compiler knows about it. Arguments and return values are covered in detail in chapter 9. A word of caution, though: many compilers include a lot of extra functions that make life even easier and are very tempting to use, but are not part of the Standard C library. If you are certain you will never want to move the application to another platform (and who is certain of that?), go ahead -use those functions and make your life easier. If you want your application to be portable, you should restrict yourself to Standard C functions (this is safe because the Standard C library is part of C++). Keep a guide to Standard C handy and refer to that when looking for a function rather than your local C or C++ guide. If you must perform platform-specific activities, try to isolate that code in one spot so it can easily be changed when porting to another platform. Platform-specific activities are often encapsulated in a class - this is the ideal solution. The formula for using a library function is as follows: first, find the function in your guidebook (many guidebooks will index the function by category as well as alphabetically). The description of the function should include a section that demonstrates the syntax of the code. The top of this section usually has at least one #include line, showing you the header file containing the function prototype. Duplicate this #include line in your file, so the function is properly declared. Now you can call the function in the same way it appears in the syntax section. If you make a mistake, the compiler will discover it by comparing your function call to the function prototype in the header, and tell you about your error. The linker searches the standard library by default, so that's all you need to do: include the header file, and call the function. Creating your own libraries with the librarian You can collect your own functions together into a library, or add new functions to the library the linker secretly searches (you should back up the old one before doing this). Most packages come with a librarian that manages groups of object modules. Each librarian has its own commands, but the general idea is this: if you want to create a library, make a header file containing the function prototypes for all the functions in your library. Now take all the object modules and hand them to the librarian along with a name for the finished library (most librarians require a common extension, such as.LIB). Place the finished library in the same spot the other libraries reside, so the linker can find it. When you use your library, you will have to add something to the command line so the linker knows to search the library for the functions you call. You must find all the details in your local manual, since they vary from system to system. The header file When you create a class, you are creating a new data type. Generally, you want this type to be easily accessible to yourself and others. In addition, you want to separate the interface (the class declaration) from the implementation (the definition of the class member functions) so the implementation can be changed without forcing a re-compile of the entire system. You achieve this end by putting the class declaration in a header file. The definitions of the class member functions are also separated into their own file. The member functions are debugged and compiled once, and are then available as an object module (or in a library, if the librarian is used) for anyone who wants to use the class. The user of the class simply includes the header file, creates objects (instances) of that class, and links in the object module or library (i.e.: the compiled code). The concept of a collection of associated functions combined into the same object module or library, and a header file containing all the declarations for the functions, is very standard when building large projects in C. It is de rigueur in C++: you could throw any function into a collection in C, but the class in C++ determines which functions are associated by dint of their common access to the private data. Any member function for a class must be declared in the class declaration; you cannot put it in some separate file. You may want the compiler to speed up just a bit by avoiding the task of opening and including the file. For example, here's an extremely lazy declaration of the C function printf( ): printf(...); It says: printf( ) has some number of arguments, and they all have some type but just take whatever arguments you see and accept them. By using this kind of declaration, you suspend all error checking on the arguments. This practice can cause subtle problems. If you declare functions by hand in each different file, you may make a mistake the compiler accepts in a particular file. The program will link correctly, but the use of the function in that one file will be faulty. This is a tough error to find, and is easily avoided. If you place all your function declarations in a header file, and include that file everywhere you use the function (and especially where you define the function) you insure a consistent declaration across the whole system. You also insure that the declaration and the definition match by including the header in the definition file. C does not enforce this practice. It is very easy, for instance, to leave the header file out of the function definition file. Header files often confuse the novice programmer (who may ignore them or use them improperly). If a class is declared in a header file in C++, you must include the header file everywhere a class is used and where class member functions are defined. The compiler will give an error message if you try to call a function without declaring it first. By enforcing the proper use of header files, the language ensures consistency in libraries, and reduces bugs by forcing the same interface to be used everywhere. There was an additional problem in earlier releases of the language. When you overloaded ordinary (non-member) functions, the order of overloading was important. If you used the same function names in separate header files, you could change the order of overloading without knowing it, simply by including the files in a different order. The compiler didn't complain, but the linker did - it was mystifying. This problem existed in C++ compilers following ATandT releases up through 1.2. It was solved by a change in the language called type-safe linkage (described later in the book). Preventing re-declaration of classes When you put a class declaration in a header file, it is possible for the file to be included more than once in a complicated program. The streams class is a good example. If the file you are working on uses more than one kind of class, you run the risk of including the streams header more than once and re-declaring streams. The compiler considers the re-declaration of a class to be an error, since it would otherwise allow you to use the same name for different classes. To prevent this error when multiple header files are included, you need to build some intelligence into your header files using the preprocessor (the streams class already has this intelligence). The preprocessor directives #define, #ifdef and #endif As shown earlier in this chapter, #define will create preprocessor macros that look similar to function definitions. The complement of #define is #undef (short for un-define), which will make an #ifdef statement using the same variable yield a false result. The complement of #ifdef is #ifndef, which will yield a true if the label has not been defined (this is the one we use in header files). There are other useful features in the C preprocessor. You should check your local guide for the full set. Standard for each class header file In each header file that contains a class, you should first check to see if the file has already been included in this particular code file. You do this by checking a preprocessor flag. If the flag isn't set, the file wasn't included and you should set the flag (so the class can't get re-declared) and declare the class. If the flag was set the class has already been declared so you should just ignore the code declaring the class. Portable inclusion of header files C++ was created in a Unix environment, where the file names have case sensitivity. Thus you will sometimes see old header files with these extensions. All header files in this book use the .h convention. As you can imagine, the struct is an early attempt at abstract data typing (without the associated member functions). In C, you must create non-member functions that take your struct as an argument. There is no concept of private data, so anyone (not just the functions you define) can change the elements of a struct. C++ will accept any struct you can declare in C (so it's upward compatible). However, C++ expands the definition of a struct so it is just like a class, except a class defaults to private while a struct defaults to public. Any struct you define in C++ can have member functions, constructors and a destructor, etc. Although the struct is an artifact from C it emphasizes that all elements are public. You can make a class in C++ work just like a struct in C++ by putting public: at the beginning of your class. Notice that a struct in Standard C doesn't have constructors, destructors or member functions. The enum keyword (from C) automatically enumerates any list of words you give it by assigning them values of 0, 1, 2, etc. You can declare enum variables (which are always ints). The declaration of an enum looks similar to a class declaration, but an enum cannot have any member functions. Since shape is really just an int, however, it can be any value an int can hold (including a negative number). You can also compare an int variable with a value in the enumeration. You can see how much more readable the code is when you use enumerated data types. Saving memory with union Sometimes a program will handle different types of data using the same variable. In this situation, you have two choices: you can create a class or struct containing all the possible different types you might need to store, or you can use a union. A union piles all the data into a single space; it figures out the amount of space necessary for the largest item you've put in the union, and makes that the size of the union. Use a union to save memory. Anytime you place a value in a union, the value always starts in the same place at the beginning of the union, but only uses as much space as is necessary. Thus, you create a super-variable, capable of holding any of the union variables. All the addresses of the union variables are the same (in a class or struct, the addresses are different). Here's a simple use of a union. Try removing various elements and see what effect it has on the size of the union. Notice that it makes no sense to declare more than one instance of a single data type in a union (unless you're just doing it to use a different name). Once you perform an assignment, the compiler doesn't care what you do with the union. This is acceptable if you are going to immediately define instances of the enum, as is done here. There is no need to refer to the enum's type in the future, so the type is optional. The union has no type name and no variable name. This is called an anonymous union, and creates space for the union but doesn't require accessing the union elements with a variable name and the dot operator. The only difference is that both variables occupy the same space. If the anonymous union is at file scope (outside all functions and classes) then it must be declared static so it has internal linkage. Debugging flags If you hard-wire your debugging code into a program, you can run into problems. You start to get too much information, which makes the bugs difficult to isolate. When you think you've found the bug you start tearing out debugging code, only to find you need to put it back in again. You can solve these problems with two types of flags: preprocessor debugging flags and run-time debugging flags. Preprocessor debugging flags By using the preprocessor to #define one or more debugging flags (preferably in a header file), you can test a flag using a #ifdef statement to conditionally include debugging code. When you think your debugging is finished, you can simply #undef the flag(s) and the code will automatically be removed (and you'll reduce the size of your executable file). It is best to decide on names for debugging flags before you begin building your project so the names will be consistent. Preprocessor flags are often distinguished from variables by writing them in all upper case. A common flag name is simply DEBUG (but be careful you don't use NDEBUG, which is reserved in Standard C). Check your local guide for details. Run-time debugging flags In some situations it is more convenient to turn debugging flags on and off during program execution (it is much more elegant to turn flags on and off when the program starts up using the command line. See chapter 4 for details of using the command line). Large programs are tedious to recompile just to insert debugging code. You can create integer flags and use the fact that nonzero values are true to increase the readability of your code. Turning a variable name into a string When writing debugging code, it is tedious to write print expressions consisting of a string containing the variable name followed by the variable. Fortunately, Standard C has introduced the string-ize operator #. When you put a # before an argument in a preprocessor macro, that argument is turned into a string by putting quotes around it. When you use assert( ), you give it an argument that is an expression you are asserting to be true. The preprocessor generates code that will test the assertion. If the assertion isn't true, the program will stop after issuing an error message telling you what the assertion was and that it failed. When you are finished debugging, you can remove the code generated by the macro simply by placing the line: #define NDEBUG in the program before the inclusion of assert.h, or by defining NDEBUG on the compiler command line. NDEBUG is a flag used in assert.h to change the way code is generated by the macros. Debugging techniques combined By combining the techniques discussed in this section, a framework arises that you can follow when writing your own debugging code. Keep in mind that if you want to isolate certain types of debugging code you can create variables debug1, debug2, etc., and preprocessor flags DEBUG1, DEBUG2, etc. The following example shows the use of command-line flags, formally introduced in the next chapter. It is better to show you the right way to do something and risk confusing you for a bit rather than teaching you some method that will later need to be un-learned. The flags on the command line are accessed through the arguments to main( ), called argc and argv. If you type on the command line: debug2 nothing will happen, but if you type debug2 d The debugger will be turned on. When you want to remove the debugging code at some later date to reduce the size of the executable program, simply change the #define DEBUG to a #undef DEBUG (or better yet, do it from the compiler command line). Most compilers allow you to do this with a single command-line statement. For a compiler named cpp, for example, you might say cpp Libtest.cpp lib.cpp The problem with this approach is that the compiler will first compile each individual translation unit, regardless of whether it needs to be rebuilt or not. With many files in a project, it can get very tedious to recompile everything if you've only changed a single file. The first solution to this problem, developed on Unix (which is where C was created), was a program called make. Make compares the date on the source-code file to the date on the object file, and if the object-file date is earlier than the source-code file, make invokes the compiler on the source. Because make is available in some form for virtually all C++ compilers (and even if it isn't, you can use freely-available makes with any compiler), it will be the tool used throughout this book. However, compiler vendors also came up with their own project building tools. These tools ask you which translation units are in your project, and determine all the relationships themselves. They have something similar to a makefile, generally called a project file, but the programming environment maintains this file so you don't have to worry about it. The makefiles used within this book should work regardless of whether you are also using a specific vendor's project-building tool. File names One other issue you should be aware of is file naming. It was first developed on Unix, where the operating system was aware of upper and lower case in file names. The original file names were simply capitalized versions of the C extensions: .H and .C. This of course didn't work for operating systems that didn't distinguish upper and lower case, like MS-DOS. DOS C++ vendors used extensions of .hxx and .cxx for header files and implementation files, respectively, or .hpp and .cpp. Later, someone figured out that the only reason you needed a different extension for a file was so the compiler could determine whether to compile it as a C or C++ file. Because the compiler never compiled header files directly, only the implementation file extension needed to be changed. The custom, virtually across all systems, has now become to use .cpp for implementation files and .h for header files. Make: an essential tool for separate compilation There is one more tool you should understand before creating programs in C++. The make utility manages all the individual files in a project. When you edit the files in a project, make insures that only the source files that were changed, and other files that are affected by the modified files, are re-compiled. By using make, you don't have to re-compile all the files in your project every time you make a change. Learning to use make will save you a lot of time and frustration. The C language was developed to write the Unix operating system. As programs encompassed more and more files, the job of deciding which files should be recompiled because of changes became tedious and error-prone, so make was invented. Most C compilers come with a make program. All C++ packages either come with a make, or are used with a C compiler that has a make. Make activities When you type make, the make program looks in the current directory for a file named makefile, which you've created if it's your project. This file lists dependencies between source code files. If a dependent file has an older date than a file it depends on, make executes the rule given after the dependency. All comments in makefiles start with a # and continue to the end of the line. As a simple example, the makefile for the hello program might contain: # A comment hello.exe: hello.cpp g++ hello.cpp This says that hello.exe (the target) depends on hello.cpp. When hello.cpp has a newer date than hello.exe, make executes the rule g++ hello.cpp. There may be multiple dependencies and multiple rules. All the rules must begin with a tab. By creating groups of interdependent dependency-rule sets, you can modify source code files, type make and be certain that all the affected files will be re-compiled correctly. Macros A makefile may contain macros. Macros allow convenient string replacement. The makefiles in this book use a macro to invoke the C++ compiler. For example, #Macro to invoke Gnu C++ CPP = g++ hello.exe: hello.cpp $(CPP) hello.cpp The $ and parentheses expand the macro. To expand means to replace the macro call $(CPP) with the string g++. With the above macro, if you want to change to a different compiler you just change the macro to: CPP = cpp You can also add compiler flags, etc., to the macro. In addition, ExtractCode.cpp creates a makefile in each subdirectory so that you can simply move into that subdirectory and type make. Finally, ExtractCode.cpp creates a master makefile in the root directory where the book's files are expanded, and this makefile descends into each subdirectory and calls make. Because implementations of make vary from system to system, only the the most basic, common features are used in the generated makefiles. You should be aware that there are many advanced shortcuts that can save a lot of time when using make. Your local documentation will describe the further features of your particular make. An example makefile As mentioned before, the makefile for each chapter will be automatically generated by the code-extraction tool ExtractCode.cpp that is shown and described in Chapter XX. Thus, the makefile for each chapter will not be placed in the book. To use a different compiler, you can either edit the makefile or change the value of the macro on the command line, like this: make CPP=cpp The second macro OFLAG is the flag that's used to indicate the name of the output file. You can see that this makefile takes the absolute safest route of using as few make features as possible - it only uses the basic make concepts of targets and dependencies, as well as macros. This way it is virtually assured of working with as many make programs as possible. It tends to produce a much larger makefile, but that's not so bad since it's automatically generated by ExtractCode.cpp. One of the features not used here is called rules (or implicit rules or inference rules). This eliminates a lot of redundancy in a makefile. Once you teach make the rules for producing one kind of file from another, all you have to do is tell make which files depend on which other files. When make finds a file with a date earlier than the file it depends on (which means the source file has been changed and not yet recompiled), it uses the rule to create a new file. The implicit rule tells make that it doesn't need explicit rules to build everything, but instead it can figure out how to build things based on their file extension. In this case it says: to build a file that ends in.exe from one which ends in .cpp, invoke the following command. The command is the compiler name, followed by a special built-in macro. Although the makefile contains no explicit dependencies, the implicit conversion implies the proper dependencies. In the above makefile the dummy target is called all. When a line is too long in a makefile, you can continue it on the next line by using a backslash (\). White space is ignored here, so you can format for readability. Summary Exercises 4: Data abstraction C++ is a productivity enhancement tool. It's because you've become convinced that you're going to get big gains by using this new tool. Productivity, in computer programming terms, means that fewer people can make much more complex and impressive programs in less time. These are certainly important factors that will be examined in this book. But raw productivity means a program that might take three of you a week takes one of you a day or two. This touches several levels of economics. The only way to get massive increases in productivity is to leverage off other people's code, that is, to use libraries. A library is simply a bunch of code that someone else has written, packaged together somehow. Often, the most minimal package is a file with an extension like.LIB and one or more header files to declare what's in the library to your compiler. The linker knows how to search through the LIB file and extract the appropriate compiled code. But that's only one way to deliver a library. On platforms that span many architectures, like Unix, often the only sensible way to deliver a library is with source code, so it can be recompiled on the new target. And on Microsoft Windows, the dynamic-link library (DLL) is a much more sensible approach - for one thing, you can often update your program by sending out a new DLL, which your library vendor may have sent you. So libraries are probably the most important way to improve productivity, and one of the primary design goals of C++ is to make library use easier. This implies that there's something hard about using libraries in C. Understanding this factor will give you a first insight into the design of C++, and thus insight into how to use it. Declarations vs. A declaration introduces a name to the compiler. It says, Here's what this name means. A definition, on the other hand, allocates storage for the name. This meaning works whether you're talking about a variable or a function; in either case, at the point of definition the compiler allocates storage. For a variable, it determines how big that variable is and generates space in memory to hold information. For a function, the compiler generates code, which ends up allocating storage in memory. The storage for a function has an address that can be produced using the function name with no argument list, or with the address-of operator. A definition can also be a declaration. If the compiler hasn't seen the name A before and you define int A, the compiler sees the name for the first time and allocates storage for it all at once. Declarations are often made using the extern keyword. With a function declaration, extern is optional because a function name, argument list, or a return value without a function body is automatically a declaration. A function prototype contains all the information about argument types and return values. C++ provides function prototyping because it adds a significant level of safety. In the definitions, they are required. This is true only in C, not C++. Throughout this book you'll notice that the first line of a file will be a comment that starts with the open-comment syntax followed by a colon. There are also characteristics (blue, pounds, texture, luminance), which are represented by data. And when you start to deal with a set of characteristics in C, it is very convenient to clump them together into a struct, especially if you want to represent more than one similar thing in your problem space. Then you can make a variable of this struct for each thing. Thus, most C libraries have a set of structs and a set of functions that act on those structs. As an example of what such a system looks like, consider a programming tool that acts like an array, but whose size can be established at run-time, when it is created. For example, when creating a linked list, you need a pointer to the next struct. But almost universally in a C library you'll see the typedef as shown above, on every struct in the library. You aren't just introducing a function name; you're also telling the compiler what the argument list and return value look like. The storage pointer is an unsigned char*. This is the smallest piece of storage a C compiler supports, although on some machines it can be the same size as the largest. It's implementation dependent. You might think that because the stash is designed to hold any type of variable, a void* would be more appropriate here. However, the purpose is not to treat this storage as a block of some unknown type, but rather as a block of contiguous bytes. By doing this, you can easily create another directory off the book's root and copy code to it for experimentation without worrying about changing #include paths. Initially, the storage pointer is set to zero, and the size indicator is also zero - no initial storage is allocated. The add( ) function inserts an element into the stash at the next available location. First, it checks to see if there is any available space left. If not, it expands the storage using the inflate( ) function, described later. Because the compiler doesn't know the specific type of the variable being stored (all the function gets is a void*), you can't just do an assignment, which would certainly be the convenient thing. Instead, you must use the Standard C library function memcpy( ) to copy the variable byte-by-byte. The first argument is the destination address where memcpy( ) is to start copying bytes. This number, which is simply a count of the number of pieces used plus one, must be multiplied by the number of bytes occupied by each piece to produce the offset in bytes. This doesn't produce the address, but instead the byte at the address. To produce the address, you must use the address-of operator and. The second and third arguments to memcpy( ) are the starting address of the variable to be copied and the number of bytes to copy, respectively. The next counter is incremented, and the index of the value stored is returned, so the programmer can use it later in a call to fetch( ) to select that element. It seems like a lot of trouble to go through to do something that would probably be a lot easier to do by hand. If you have a struct stash called intStash, for example, it would seem much more straightforward to find out how many elements it has by saying intStash.next instead of making a function call (which has overhead) like count(andintStash). However, if you wanted to change the internal representation of stash and thus the way the count was calculated, the function call interface allows the necessary flexibility. But alas, most programmers won't bother to find out about your better design for the library. They'll look at the struct and grab the next value directly, and possibly even change next without your permission. If only there were some way for the library designer to have better control over things like this! The heap is a big block of memory used for allocating smaller pieces at run-time. You use the heap when you don't know the size of the memory you'll need while you're writing a program. That is, only at run-time will you find out that you need space to hold 200 airplane variables instead of 20. Dynamic-memory allocation functions are part of the Standard C library and include malloc( ), calloc( ), realloc( ), and free( ). The inflate( ) function uses realloc( ) to get a bigger chunk of space for the stash. If the size is smaller, there's no chance the block will need to be copied, so the heap manager is simply told that the extra space is free. If the size is larger, as in inflate( ),there may not be enough contiguous space, so a new chunk might be allocated and the memory copied. The assert( ) checks to make sure that the operation was successful. It gives you chunks of memory and takes them back when you free( ) them. There's no facility for heap compaction, which compresses the heap to provide bigger free chunks. If a program allocates and frees heap storage for a while, you can end up with a heap that has lots of memory free, just not anything big enough to allocate the size of chunk you're looking for at the moment. However, a heap compactor moves memory chunks around, so your pointers won't retain their proper values. The macro says, I assert this to be true, and if it's not, the program will exit after printing an error message. When you are no longer debugging, you can define a flag so asserts are ignored. In the meantime, it is a very clear and portable way to test for errors. Unfortunately, it's a bit abrupt in its handling of error situations: Sorry, mission control. Our C program failed an assertion and bailed out. We'll have to land the shuttle on manual. In Chapter 16, you'll see how C++ provides a better solution to critical errors with exception handling. When you create a variable on the stack at compile-time, the storage for that variable is automatically created and freed by the compiler. It knows exactly how much storage it needs, and it knows the lifetime of the variables because of scoping. With dynamic memory allocation, however, the compiler doesn't know how much storage you're going to need, and it doesn't know the lifetime of that storage. It doesn't get cleaned up automatically. Therefore, you're responsible for releasing the storage using free( ), which tells the heap manager that storage can be used by the next call to malloc( ), calloc( ) or realloc( ). The logical place for this to happen in the library is in the cleanup( ) function because that is where all the closing-up housekeeping is done. To test the library, two stashes are created. The first holds ints and the second holds arrays of 80 chars. Of course, you must remember to initialize these later in the block. One of the problems with libraries is that you must carefully convey to the user the importance of the initialization and cleanup functions. If these functions aren't called, there will be a lot of trouble. Unfortunately, the user doesn't always wonder if initialization and cleanup are mandatory. They know what they want to accomplish, and they're not as concerned about you jumping up and down saying, Hey, wait, you have to do this first! Some users have even been known to initialize the elements of the structure themselves. There's certainly no mechanism to prevent it (more foreshadowing). The intStash is filled up with integers, and the stringStash is filled with strings. These strings are produced by opening the source code file, Libtest.c, and reading the lines from it into the stringStash. Notice something interesting here: The Standard C library functions for opening and reading files use the same techniques as in the stash library! One of the things fclose( ) does is release the FILE struct back to the heap. Once you start noticing this pattern of a C library consisting of structs and associated functions, you see it everywhere! After the two stashes are loaded, you can print them out. The intStash is printed using a for loop, which uses count( ) to establish its limit. The stringStash is printed with a while, which breaks out when fetch( ) returns zero to indicate it is out of bounds. There are a number of other things you should understand before we look at the problems in creating a C library. It's possible in C to call a function that you haven't declared. A good compiler will warn you that you probably ought to declare a function first, but it isn't enforced. This is a dangerous practice, because the compiler can assume that a function that you call with an int argument has an argument list containing int, and it will treat it accordingly - a very difficult bug to find. Note that the Lib.h header file must be included in any file that refers to stash because the compiler can't even guess at what that structure looks like. It can guess at functions, even though it probably shouldn't, but that's part of the history of C. Each separate C file is a translation unit. That is, the compiler is run separately on each translation unit, and when it is running it is aware of only that unit. Thus, any information you provide by including header files is quite important because it provides the compiler's understanding of the rest of your program. Declarations in header files are particularly important, because everywhere the header is included, the compiler will know exactly what to do. If, for example, you have a declaration in a header file that says void foo(float);, the compiler knows that if you call it with an integer argument, it should promote the int to a float. Without the declaration, the compiler would simply assume that a function foo(int) existed, and it wouldn't do the promotion. For each translation unit, the compiler creates an object file, with an extension of.o or .obj or something similar. These object files, along with the necessary start-up code, must be collected by the linker into the executable program. During linking, all the external references must be resolved. For example, in Libtest.c, functions like initialize( ) and fetch( ) are declared (that is, the compiler is told what they look like) and used, but not defined. They are defined elsewhere, in Lib.c. Thus, the calls in Libtest.c are external references. The linker must, when it puts all the object files together, take the unresolved external references and find the addresses they actually refer to. Those addresses are put in to replace the external references. It's important to realize that in C, the references are simply function names, generally with an underscore in front of them. So all the linker has to do is match up the function name where it is called and the function body in the object file, and it's done. If you accidentally made a call that the compiler interpreted as foo(int) and there's a function body for foo(float) in some other object file, the linker will see _foo in one place and _foo in another, and it will think everything's OK. The foo( ) at the calling location will push an int onto the stack, and the foo( ) function body will expect a float to be on the stack. If the function only reads the value and doesn't write to it, it won't blow up the stack. In fact, the float value it reads off the stack might even make some kind of sense. That's worse because it's harder to find the bug. What's wrong? We are remarkably adaptable, even with things where perhaps we shouldn't adapt. The style of the stash library has been a staple for C programmers, but if you look at it for a while, you might notice that it's rather. When you use it, you have to pass the address of the structure to every single function in the library. When reading the code, the mechanism of the library gets mixed with the meaning of the function calls, which is confusing when you're trying to understand what's going on. One of the biggest obstacles, however, to using libraries in C is the problem of name clashes. C has a single name space for functions; that is, when the linker looks for a function name, it looks in a single master list. In addition, when the compiler is working on a translation unit, it can only work with a single function with a given name. Now suppose you decide to buy two libraries from two different vendors, and each library has a structure that must be initialized and cleaned up. Both vendors decided that initialize( ) and cleanup( ) are good names. If you include both their header files in a single translation unit, what does the C compiler do? Fortunately, Standard C gives you an error, telling you there's a type mismatch in the two different argument lists of the declared functions. But even if you don't include them in the same translation unit, the linker will still have problems. A good linker will detect that there's a name clash, but some linkers take the first function name they find, by searching through the list of object files in the order you give them in the link list. To solve this problem, C library vendors will often prepend a string of unique characters to the beginning of all their function names. So initialize( ) and cleanup( ) might become stash_initialize( ) and stash_cleanup( ). This is a logical thing to do because it mangles the name of the struct the function works on with the name of the function. Now it's time to take the very first step into C++. Variable names inside a struct do not clash with global variable names. So why not take advantage of this for function names, when those functions operate on a particular struct? That is, why not make functions members of structs? The basic object Step one in C++ is exactly that. The C++ comments only go to the end of the line, which is often very convenient. This allows an exact inclusion of the file from the source code. In addition, you can easily identify the file in the electronic source code from its name in the book listing. Next, notice there is no typedef. Instead of requiring you to create a typedef, the C++ compiler turns the name of the structure into a new type name for the program (just like int, char, float and double are type names). The use of Stash is still the same. All the data members are exactly the same as before, but now the functions are inside the body of the struct. In addition, notice that the first argument from the C version of the library has been removed. In C++, instead of forcing you to pass the address of the structure as the first argument to all the functions that operate on that structure, the compiler secretly does this for you. Now the only arguments for the functions are concerned with what the function does, not the mechanism of the function's operation. It's important to realize that the function code is effectively the same as it was with the C library. The number of arguments are the same (even though you don't see the structure address being passed in, it's still there); and there's only one function body for each function. That is, just because you say Stash A, B, C; doesn't mean you get a different add( ) function for each variable. So the code that's generated is almost the same as you would have written for the C library. Interestingly enough, this includes the name mangling you probably would have done to produce Stash_initialize( ), Stash_cleanup( ), and so on. When the function name is inside the struct, the compiler effectively does the same thing. Therefore, initialize( ) inside the structure Stash will not collide with initialize( ) inside any other structure. Most of the time you don't have to worry about the function name mangling - you use the unmangled name. But sometimes you do need to be able to specify that this initialize( ) belongs to the struct Stash, and not to any other struct. In particular, when you're defining the function you need to fully specify which one it is. To accomplish this full specification, C++ has a new operator, :: the scope resolution operator (named so because names can now be in different scopes: at global scope, or within the scope of a struct). First, the declarations in the header files are required by the compiler. In C++ you cannot call a function without declaring it first. The compiler will issue an error message otherwise. This is an important way to ensure that function calls are consistent between the point where they are called and the point where they are defined. By forcing you to declare the function before you call it, the C++ compiler virtually ensures you will perform this declaration by including the header file. If you also include the same header file in the place where the functions are defined, then the compiler checks to make sure the declaration in the header and the definition match up. This means that the header file becomes a validated repository for function declarations and ensures that functions are used consistently throughout all translation units in the project. Of course, global functions can still be declared by hand every place where they are defined and used. You can see that all the member functions are virtually the same, except for the scope resolution and the fact that the first argument from the C version of the library is no longer explicit. It's still there, of course, because the function has to be able to work on a particular struct variable. But notice that inside the member function the member selection is also gone! Of course, the C++ compiler must still be doing this for you. Indeed, it is taking the secret first argument and applying the member selector whenever you refer to one of the data members of a class. This means that whenever you are inside the member function of another class, you can refer to any member (including another member function) by simply giving its name. The compiler will search through the local structure's names before looking for a global version of that name. You'll find that this feature means that not only is your code easier to write, it's a lot easier to read. But what if, for some reason, you want to be able to get your hands on the address of the structure? In the C version of the library it was easy because each function's first argument was a stash* called S. In C++, things are even more consistent. There's a special keyword, called this, which produces the address of the struct. It's the equivalent of S in the C version of the library. Usually, you don't use this very often, but when you need it, it's there. There's one last change in the definitions. But in C++, this statement is not allowed. Because in C, you can assign a void* (which is what malloc( ), calloc( ), and realloc( ) return) to any other pointer without a cast. C is not so particular about type information, so it allows this kind of thing. Not so with C++. Type is critical in C++, and the compiler stamps its foot when there are any violations of type information. This has always been important, but it is especially important in C++ because you have member functions in structs. If you could pass pointers to structs around with impunity in C++, then you could end up calling a member function for a struct that doesn't even logically exist for that struct! A real recipe for disaster. A cast is always required, to tell the reader and the compiler that you know the type that it is going to. Thus you will see the return values of calloc( ) and realloc( ) are explicitly cast to (unsigned char*). This brings up an interesting issue. One of the important goals for C++ is to compile as much existing C code as possible to allow for an easy transition to the new language. Notice in the above example how Standard C library functions are used. This is often much more of an advantage than a hindrance. In fact, there are many situations where you are trying to run down an error in C and just can't find it, but as soon as you recompile the program in C++, the compiler points out the problem! In C, you'll often find that you can get the program to compile, but then you have to get it to work. In C++, often when the program compiles correctly, it works, too! This is because the language is a lot stricter about type. This is a convenient syntax because it mimics the selection of a data member of the structure. The difference is that this is a function member, so it has an argument list. Of course, the call that the compiler actually generates looks much more like the original C library function. Thus, considering name mangling and the passing of this, the C++ function call intStash.initialize(sizeof(int), 100) becomes something like Stash_initialize(andintStash, sizeof(int), 100). If you ever wonder what's going on underneath the covers, remember that the original C++ compiler cfront from ATandT produced C code as its output, which was then compiled by the underlying C compiler. This approach meant that cfront could be quickly ported to any machine that had a C compiler, and it helped to rapidly disseminate C++ compiler technology. You'll also notice an additional cast in while(cp = (char*)stringStash.fetch(i++)) This is due again to the stricter type checking in C++. What's an object? Now that you've seen an initial example, it's time to step back and take a look at some terminology. The act of bringing functions inside structures is the root of the changes in C++, and it introduces a new way of thinking about structures as concepts. In C, a structure is an agglomeration of data, a way to package data so you can treat it in a clump. But it's hard to think about it as anything but a programming convenience. The functions that operate on those structures are elsewhere. However, with functions in the package, the structure becomes a new creature, capable of describing both characteristics (like a C struct could) and behaviors. The concept of an object, a free-standing, bounded entity that can remember and act, suggests itself. The terms object and object-oriented programming (OOP) are not new. The first OOP language was Simula-67, created in Scandinavia in 1967 to aid in solving modeling problems. These problems always seemed to involve a bunch of identical entities (like people, bacteria, and cars) running around interacting with each other. Simula allowed you to create a general description for an entity that described its characteristics and behaviors and then make a whole bunch of them. In Simula, the general description is called a class (a term you'll see in a later chapter), and the mass-produced item that you stamp out from a class is called an object. In C++, an object is just a variable, and the purest definition is a region of storage. It's a place where you can store data, and it's implied that there are also operations that can be performed on this data. Unfortunately there's not complete consistency across languages when it comes to these terms, although they are fairly well-accepted. You will also sometimes encounter disagreement about what an object-oriented language is, although that seems to be fairly well sorted out by now. There are languages that are object-based, which means they have objects like the C++ structures-with-functions that you've seen so far. This, however, is only part of the picture when it comes to an object-oriented language, and languages that stop at packaging functions inside data structures are object-based, not object-oriented. Abstract data typing The ability to package data with functions allows you to create a new data type. This is often called encapsulation28. An existing data type, like a float, has several pieces of data packaged together: an exponent, a mantissa, and a sign bit. You can tell it to do things: add to another float or to an int, and so on. It has characteristics and behavior. The Stash is also a new data type. You can add( ) and fetch( ) and inflate( ). You create one by saying Stash S, as you create a float by saying float f. A Stash also has characteristics and behavior. Even though it acts like a real, built-in data type, we refer to it as an abstract data type, perhaps because it allows us to abstract a concept from the problem space into the solution space. In addition, the C++ compiler treats it like a new data type, and if you say a function expects a Stash, the compiler makes sure you pass a Stash to that function. The same level of type checking happens with abstract data types (sometimes called user-defined types) as with built-in types. You can immediately see a difference, however, in the way you perform operations on objects. You say object.member_function(arglist). The trick, of course, is figuring out what your objects and messages are, but once you accomplish that the implementation in C++ is surprisingly straightforward. Object details At this point you're probably wondering the same thing that most C programmers do because C is a language that is very low-level and efficiency-oriented. The size of a struct is the combined size of all its members. Sometimes when a struct is laid out by the compiler, extra bytes are added to make the boundaries come out neatly - this may increase execution efficiency. In Chapters 13 and 15, you'll see how in some cases secret pointers are added to the structure, but you don't need to worry about that right now. You can determine the size of a struct using the sizeof operator. In C, this is illegal, but in C++ we need the option of creating a struct whose sole task is to scope function names, so it is allowed. Still, the result produced by the second printf( ) statement is a somewhat surprising nonzero value. In early versions of the language, the size was zero, but an awkward situation arises when you create such objects: They have the same address as the object created directly after them, and so are not distinct. Thus, structures with no data members will always have some minimum nonzero size. The last two sizeof statements show you that the size of the structure in C++ is the same as the size of the equivalent version in C. C++ endeavors not to add any overhead. Header file etiquette When I first learned to program in C, the header file was a mystery to me. Many C books don't seem to emphasize it, and the compiler didn't enforce function declarations, so it seemed optional most of the time, except when structures were declared. In C++ the use of header files becomes crystal clear. They are practically mandatory for easy program development, and you put very specific information in them: declarations. The header file tells the compiler what is available in your library. Because you can use the library without the source code for the CPP file (you only need the object file or library file), the header file is where the interface specification is stored. The header is a contract between you and the user of your library. It says, Here's what my library does. It doesn't say how because that's stored in the CPP file, and you won't necessarily deliver the sources for how to the user. The contract describes your data structures, and states the arguments and return values for the function calls. The user needs all this information to develop the application and the compiler needs it to generate proper code. The compiler enforces the contract by requiring you to declare all structures and functions before they are used and, in the case of member functions, before they are defined. Thus, you're forced to put the declarations in the header and to include the header in the file where the member functions are defined and the file(s) where they are used. Because a single header file describing your library is included throughout the system, the compiler can ensure consistency and prevent errors. There are certain issues that you must be aware of in order to organize your code properly and write effective header files. The first issue concerns what you can put into header files. The basic rule is only declarations, that is, only information to the compiler but nothing that allocates storage by generating code or creating variables. This is because the header file will probably be included in several translation units in a project, and if storage is allocated in more than one place, the linker will come up with a multiple definition error. This rule isn't completely hard and fast. If you define a piece of data that is file static (has visibility only within a file) inside a header file, there will be multiple instances of that data across the project, but the linker won't have a collision. Basically, you don't want to do anything in the header file that will cause an ambiguity at link time. The second critical issue concerning header files is redeclaration. Both C and C++ allow you to redeclare a function, as long as the two declarations match, but neither will allow the redeclaration of a structure. In C++ this rule is especially important because if the compiler allowed you to redeclare a structure and the two declarations differed, which one would it use? In the whole project, it's very likely that you'll include several files that include the same header file. During a single compilation, the compiler can see the same header file several times. Unless you do something about it, the compiler will see the redeclaration of your structure. The typical preventive measure is to insulate the header file by using the preprocessor. If you have a header file named FOO.H, it's common to do your own name mangling to produce a preprocessor name that is used to prevent multiple inclusion of the header file. Using headers in projects When building a project in C++, you'll usually create it by bringing together a lot of different types (data structures with associated functions). You'll usually put the declaration for each type or group of associated types in a separate header file, then define the functions for that type in a translation unit. When you use that type, you must include the header file to perform the declarations properly. Sometimes that pattern will be followed in this book, but more often the examples will be very small, so everything - the structure declarations, function definitions, and the main( ) function - may appear in a single file. However, keep in mind that you'll want to use separate files and header files in practice. Nested structures The convenience of taking data and function names out of the global name space extends to structures. You can nest a structure within another structure, and therefore keep associated elements together. If the next pointer is zero, it means you're at the end of the list. Notice that the head pointer is defined right after the declaration for struct link, instead of a separate definition link* head. This is a syntax that came from C, but it emphasizes the importance of the semicolon after the structure declaration - the semicolon indicates the end of the list of definitions of that structure type. You simply use the scope resolution operator a second time, to specify the name of the enclosing struct. The Stack::link::initialize( ) function takes the arguments and assigns them to its members. Although you can certainly do these things by hand quite easily, you'll see a different form of this function in the future, so it will make much more sense. The Stack::initialize( ) function sets head to zero, so the object knows it has an empty list. Stack::push( ) takes the argument, a pointer to the piece of data you want to keep track of using the Stack, and pushes it on the Stack. First, it uses malloc( ) to allocate storage for the link it will insert at the top. Then it calls the initialize( ) function to assign the appropriate values to the members of the link. Notice that the next pointer is assigned to the current head; then head is assigned to the new link pointer. This effectively pushes the link in at the top of the list. Stack::pop( ) stores the data pointer at the current top of the Stack; then it moves the head pointer down and deletes the old top of the Stack. Stack::cleanup( ) creates a cursor to move through the Stack and free( ) both the data in each link and the link itself. In addition, the file name is taken from the command line. Global scope resolution The scope resolution operator gets you out of situations where the name the compiler chooses by default (the nearest name) isn't what you want. For example, suppose you have a structure with a local identifier A, and you want to select a global identifier A inside a member function. The compiler would default to choosing the local one, so you must tell it to do otherwise. When you want to specify a global name using scope resolution, you use the operator with nothing in front of it. Summary In this chapter, you've learned the fundamental twist of C++: that you can place functions inside of structures. This new type of structure is called an abstract data type, and variables you create using this structure are called objects, or instances, of that type. Calling a member function for an object is called sending a message to that object. The primary action in object-oriented programming is sending messages to objects. This establishes a clear boundary between what the user of the structure can change and what only the programmer may change. Exercises 4. Create a struct declaration with a single member function; then create a definition for that member function. Create an object of your new data type, and call the member function. 5. Write and compile a piece of code that performs data member selection and a function call using the this keyword (which refers to the address of the current object). 6. Show an example of a structure declared within another structure (a nested structure). Also show how members of that structure are defined. 7. How big is a structure? Write a piece of code that prints the size of various structures. Create structures that have data members only and ones that have data members and function members. Then create a structure that has no members at all. Print out the sizes of all these. Explain the reason for the result of the structure with no data members at all. 8. C++ automatically creates the equivalent of a typedef for enumerations and unions as well as structs, as you've seen in this chapter. These are all convenient - they help you organize your code and make it easier to write and read. However, there are other important issues when making libraries easier in C++, especially the issues of safety and control. This chapter looks at the subject of boundaries in structures. Setting limits In any relationship it's important to have boundaries that are respected by all parties involved. When you create a library, you establish a relationship with the user (also called the client programmer) of that library, who is another programmer, but one putting together an application or using your library to build a bigger library. In a C struct, as with most things in C, there are no rules. Users can do anything they want with that struct, and there's no way to force any particular behaviors. For example, even though you saw in the last chapter the importance of the functions named initialize( ) and cleanup( ), the user could choose whether to call those functions or not. Everything's naked to the world. There are two reasons for controlling access to members. The first is to keep users' hands off tools they shouldn't touch, tools that are necessary for the internal machinations of the data type, but not part of the interface that users need to solve their particular problems. This is actually a service to users because they can easily see what's important to them and what they can ignore. The second reason for access control is to allow the library designer to change the internal workings of the structure without worrying about how it will affect the client programmer. In the Stack example in the last chapter, you might want to allocate the storage in big chunks, for speed, rather than calling malloc( ) each time an element is added. If the interface and implementation are clearly separated and protected, you can accomplish this and require only a relink by the user. C++ access control C++ introduces three new keywords to set the boundaries in a structure: public, private, and protected. Their use and meaning are remarkably straightforward. These access specifiers are used only in a structure declaration, and they change the boundary for all the declarations that follow them. Whenever you use an access specifier, it must be followed by a colon. Of course, neither can member functions of other structures. Only the functions that are clearly stated in the structure declaration (the contract) can have access to private members. There is no required order for access specifiers, and they may appear more than once. They affect all the members declared after them and before the next access specifier. But inheritance won't be introduced until Chapter 12, so this doesn't have any meaning to you. For the current purposes, consider protected to be just like private; it will be clarified when inheritance is introduced. Friends What if you want to explicitly grant access to a function that isn't a member of the current structure? This is accomplished by declaring that function a friend inside the structure declaration. It's important that the friend declaration occurs inside the structure declaration because you (and the compiler) must be able to read the structure declaration and see every rule about the size and behavior of that data type. And a very important rule in any relationship is who can access my private implementation? The class controls which code has access to its members. There's no magic way to break in; you can't declare a new class and say hi, I'm a friend of Bob! and expect to see the private and protected members of Bob. You can declare a global function as a friend, and you can also declare a member function of another structure, or even an entire structure, as a friend. This is a bit of a conundrum because the C++ compiler requires you to declare everything before you can refer to it, so struct Y must be declared before its member Y::f(X*) can be declared as a friend in struct X. But for Y::f(X*) to be declared, struct X must be declared first! Here's the solution. Notice that Y::f(X*) takes the address of an X object. This is critical because the compiler always knows how to pass an address, which is of a fixed size regardless of the object being passed, even if it doesn't have full information about the size of the type. If you try to pass the whole object, however, the compiler must see the entire structure definition of X, to know the size and how to pass it, before it allows you to declare a function such as Y::g(X). By passing the address of an X, the compiler allows you to make an incomplete type specification of X prior to declaring Y::f(X*). This is accomplished in the declaration struct X;. This simply tells the compiler there's a struct by that name, so if it is referred to, it's OK, as long as you don't require any more knowledge than the name. Now, in struct X, the function Y::f(X*) can be declared as a friend with no problem. If you tried to declare it before the compiler had seen the full specification for Y, it would have given you an error. This is a safety feature to ensure consistency and eliminate bugs. Notice the two other friend functions. The first declares an ordinary global function g( ) as a friend. But g( ) has not been previously declared at the global scope! It turns out that friend can be used this way to simultaneously declare the function and give it friend status. This extends to entire structures: friend struct Z is an incomplete type specification for Z, and it gives the entire structure friend status. Nested friends Making a structure nested doesn't automatically give it access to private members. To accomplish this you must follow a particular form: first define the nested structure, then declare it as a friend using full scoping. The structure definition must be separate from the friend declaration, otherwise it would be seen by the compiler as a nonmember. Because pointer is strongly associated with holder, it's sensible to make it a member of that class. Once pointer is defined, it is granted access to the private members of holder by saying: friend holder::pointer; Notice that the struct keyword is not necessary because the compiler already knows what pointer is. Because pointer is a separate class from holder, you can make more than one of them in main( ) and use them to select different parts of the array. Because pointer is a class instead of a raw C pointer, you can guarantee that it will always safely point inside the holder. Is it pure? The class definition gives you an audit trail, so you can see from looking at the class which functions have permission to modify the private parts of the class. If a function is a friend, it means that it isn't a member, but you want to give permission to modify private data anyway, and it must be listed in the class definition so all can see that it's one of the privileged functions. C++ is a hybrid object-oriented language, not a pure one, and friend was added to get around practical problems that crop up. It's fine to point out that this makes the language less pure, because C++ is designed to be pragmatic, not to aspire to an abstract ideal. Object layout Chapter 1 stated that a struct written for a C compiler and later compiled with C++ would be unchanged. This referred primarily to the object layout of the struct, that is, where the storage for the individual variables is positioned in the memory allocated for the object. If the C++ compiler changed the layout of C structs, then any C code you wrote that inadvisably took advantage of knowledge of the positions of variables in the struct would break. When you start using access specifiers, however, you've moved completely into the C++ realm, and things change a bit. Within a particular access block (a group of declarations delimited by access specifiers), the variables are guaranteed to be laid out contiguously, as in C. However, the access blocks themselves may not appear in the object in the order that you declare them. The language specification doesn't want to restrict this kind of advantage. Access specifiers are part of the structure and don't affect the objects created from the structure. All of the access specification information disappears before the program is run; generally this happens during compilation. In a running program, objects become regions of storage and nothing more. Thus, if you really want to you can break all the rules and access memory directly, as you can in C. C++ is not designed to prevent you from doing unwise things. It just provides you with a much easier, highly desirable alternative. In general, it's not a good idea to depend on anything that's implementation-specific when you're writing a program. When you must, those specifics should be encapsulated inside a structure, so any porting changes are focused in one place. The class Access control is often referred to as implementation hiding. Including functions within structures (encapsulation) produces a data type with characteristics and behaviors, but access control puts boundaries within that data type, for two important reasons. The first is to establish what users can and can't use. You can build your internal mechanisms into the structure without worrying that users will think it's part of the interface they should be using. This feeds directly into the second reason, which is to separate the interface from the implementation. If the structure is used in a set of programs, but users can't do anything but send messages to the public interface, then you can change anything that's private without requiring modifications to their code. Encapsulation and implementation hiding together invent something more than a C struct. That's what the structure declaration has become, a description of the way all objects of this type will look and act. In the original OOP language, Simula-67, the keyword class was used to describe a new data type. This apparently inspired Stroustrup to choose the same keyword for C++, to emphasize that this was the focal point of the whole language, the creation of new data types that are more than C structs with functions. This certainly seems like adequate justification for a new keyword. However, the use of class in C++ comes close to being an unnecessary keyword. It's identical to the struct keyword in absolutely every way except one: class defaults to private, whereas struct defaults to public. Indeed, the only reasons all the other members must be declared in the class at all are so the compiler knows how big the objects are and can allocate them properly, and so it can guarantee consistency. Modifying Stash to use access control It makes sense to take the examples from Chapter 1 and modify them to use classes and access control. Notice how the user portion of the interface is now clearly distinguished, so there's no possibility of users accidentally manipulating a part of the class that they shouldn't. This means that, sometime later, you can change the underlying implementation to use a different system for memory management. Other than the name of the include file, the above header is the only thing that's been changed for this example. The implementation file and test file are the same. Modifying stack to use access control As a second example, here's the Stack turned into a class. The test, too, is identical. The only thing that's been changed is the robustness of the class interface. The real value of access control is during development, to prevent you from crossing boundaries. In fact, the compiler is the only one that knows about the protection level of class members. There is no information mangled into the member name that carries through to the linker. All the protection checking is done by the compiler; it's vanished by run-time. Notice that the interface presented to the user is now truly that of a push-down stack. It happens to be implemented as a linked list, but you can change that without affecting what the user interacts with, or (more importantly) a single line of client code. Handle classes Access control in C++ allows you to separate interface from implementation, but the implementation hiding is only partial. The compiler must still see the declarations for all parts of an object in order to create and manipulate it properly. You could imagine a programming language that requires only the public interface of an object and allows the private implementation to be hidden, but C++ performs type checking statically (at compile time) as much as possible. This means that you'll learn as early as possible if there's an error. It also means your program is more efficient. However, including the private implementation has two effects: The implementation is visible even if you can't easily access it, and it can cause needless recompilation. Visible implementation Some projects cannot afford to have their implementation visible to the end user. It may show strategic information in a library header file that the company doesn't want available to competitors. You may be working on a system where security is an issue - an encryption algorithm, for example - and you don't want to expose any clues in a header file that might enable people to crack the code. Or you may be putting your library in a hostile environment, where the programmers will directly access the private components anyway, using pointers and casting. In all these situations, it's valuable to have the actual structure compiled inside an implementation file rather than exposed in a header file. Reducing recompilation The project manager in your programming environment will cause a recompilation of a file if that file is touched or if another file it's dependent upon - that is, an included header file - is touched. This means that any time you make a change to a class, whether it's to the public interface or the private implementation, you'll force a recompilation of anything that includes that header file. For a large project in its early stages this can be very unwieldy because the underlying implementation may change often; if the project is very big, the time for compiles can prohibit rapid turnaround. Thus, as long as the interface is unchanged, the header file is untouched. The implementation can change at will, and only the implementation file needs to be recompiled and relinked with the project. Here's a simple example demonstrating the technique. The line struct cheshire; is an incomplete type specification or a class declaration (A class definition includes the body of the class.) It tells the compiler that cheshire is a structure name, but nothing about the struct. This is only enough information to create a pointer to the struct; you can't create an object until the structure body has been provided. This storage is used in lieu of all the data elements you'd normally put into the private section of the class. When you compile HANDLE.CPP, this structure definition is hidden away in the object file where no one can see it. If you change the elements of cheshire, the only file that must be recompiled is HANDLE.CPP because the header file is untouched. The use of Handle is like the use of any class: Include the header, create objects, and send messages. Thus, although this isn't perfect implementation hiding, it's a big improvement. Summary Access control in C++ is not an object-oriented feature, but it gives valuable control to the creator of a class. The users of the class can clearly see exactly what they can use and what to ignore. More important, though, is the ability to ensure that no user becomes dependent on any part of the underlying implementation of a class. If you know this as the creator of the class, you can change the underlying implementation with the knowledge that no client programmer will be affected by the changes because they can't access that part of the class. When you have the ability to change the underlying implementation, you can not only improve your design at some later time, but you also have the freedom to make mistakes. No matter how carefully you plan and design, you'll make mistakes. Knowing that it's relatively safe to make these mistakes means you'll be more experimental, you'll learn faster, and you'll finish your project sooner. The public interface to a class is what the user does see, so that is the most important part of the class to get right during analysis and design. But even that allows you some leeway for change. If you don't get the interface right the first time, you can add more functions, as long as you don't remove any that client programmers have already used in their code. Exercises 1. Create a class with public, private, and protected data members and function members. Create an object of this class and see what kind of compiler messages you get when you try to access all the class members. 2. Create a class and a global friend function that manipulates the private data in the class. This not only provides a single unified point of entry into a library component, but it also hides the names of the functions within the class name. In Chapter 2, access control (implementation hiding) was introduced. This gives the class designer a way to establish clear boundaries for determining what the user is allowed to manipulate and what is off limits. It means the internal mechanisms of a data type's operation are under the control and discretion of the class designer, and it's clear to users what members they can and should pay attention to. Together, encapsulation and implementation hiding make a significant step in improving the ease of library use. The concept of new data type they provide is better in some ways than the existing built-in data types inherited from C. The C++ compiler can now provide type-checking guarantees for that data type and thus ensure a level of safety when that data type is being used. When it comes to safety, however, there's a lot more the compiler can do for us than C provides. For this reason, you will soon get used to the unlikely sounding scenario that a C++ program that compiles usually runs right the first time. Two of these safety issues are initialization and cleanup. A large segment of C bugs occur when the programmer forgets to initialize or clean up a variable. This is especially true with libraries, when users don't know how to initialize a struct, or even that they must. In C++ the concept of initialization and cleanup is essential to making library use easy and to eliminating the many subtle bugs that occur when the user forgets to perform these activities. This chapter examines the features in C++ that help guarantee proper initialization and cleanup. Guaranteed initialization with the constructor Both the Stash and Stack classes have had functions called initialize( ), which hint that it should be called before using the object in any other way. Unfortunately, this means the user must ensure proper initialization. Users are prone to miss details like initialization in their headlong rush to make your amazing library solve their problem. In C++ initialization is too important to leave to the user. The class designer can guarantee initialization of every object by providing a special function called the constructor. If a class has a constructor, the compiler automatically calls that constructor at the point an object is created, before users can even get their hands on the object. The constructor call isn't even an option for the user; it is performed by the compiler at the point the object is defined. The next challenge is what to name this function. There are two issues. The first is that any name you use is something that can potentially clash with a name you might like to use as a member in the class. The second is that because the compiler is responsible for calling the constructor, it must always know which function to call. The solution Stroustrup chose seems the easiest and most logical: The name of the constructor is the same as the name of the class. It makes sense that such a function will be called automatically on initialization. But when the program reaches the sequence point (point of execution) where a is defined, the constructor is called automatically. That is, the compiler quietly inserts the call to X::X( ) for the object a at its point of definition. Like any member function, the first (secret) argument to the constructor is the address of the object for which it is being called. Like any function, the constructor can have arguments to allow you to specify how an object is created, give it initialization values, and so on. Constructor arguments provide you with a way to guarantee that all parts of your object are initialized to appropriate values. However, it eliminates a large class of problems and makes the code easier to read. In the preceding code fragment, for example, you don't see an explicit function call to some initialize( ) function that is conceptually separate from definition. In C++, definition and initialization are unified concepts - you can't have one without the other. Both the constructor and destructor are very unusual types of functions: They have no return value. This is distinctly different from a void return value, where the function returns nothing but you still have the option to make it something else. Constructors and destructors return nothing and you don't have an option. The acts of bringing an object into and out of the program are special, like birth and death, and the compiler always makes the function calls itself, to make sure they happen. Guaranteed cleanup with the destructor As a C programmer, you often think about the importance of initialization, but it's rarer to think about cleanup. After all, what do you need to do to clean up an int? Just forget about it. However, with libraries, just letting go of an object once you're done with it is not so safe. What if it modifies some piece of hardware, or puts something on the screen, or allocates storage on the heap? If you just forget about it, your object never achieves closure upon its exit from this world. In C++, cleanup is as important as initialization and is therefore guaranteed with the destructor. The syntax for the destructor is similar to that for the constructor: The class name is used for the name of the function. However, the destructor is distinguished from the constructor by a leading tilde (~). In addition, the destructor never has any arguments because destruction never needs any options. You can see where the constructor gets called by the point of definition of the object, but the only evidence for a destructor call is the closing brace of the scope that surrounds the object. Yet the destructor is called, even when you use goto to jump out of a scope. Elimination of the definition block In C, you must always define all the variables at the beginning of a block, after the opening brace. This is not an uncommon requirement in programming languages (Pascal is another example), and the reason given has always been that it's good programming style. On this point, I have my suspicions. It has always seemed inconvenient to me, as a programmer, to pop back to the beginning of a block every time I need a new variable. I also find code more readable when the variable definition is close to its point of use. Perhaps these arguments are stylistic. In C++, however, there's a significant problem in being forced to define all objects at the beginning of a scope. If a constructor exists, it must be called when the object is created. However, if the constructor takes one or more initialization arguments, how do you know you will have that initialization information at the beginning of a scope? In the general programming situation, you won't. Because C has no concept of private, this separation of definition and initialization is no problem. However, C++ guarantees that when an object is created, it is simultaneously initialized. This ensures you will have no uninitialized objects running around in your system. C doesn't care; in fact, C encourages this practice by requiring you to define variables at the beginning of a block before you necessarily have the initialization information. Generally C++ will not allow you to create an object before you have the initialization information for the constructor, so you don't have to define variables at the beginning of a scope. In fact, the style of the language would seem to encourage the definition of an object as close to its point of use as possible. In C++, any rule that applies to an object automatically refers to an object of a built-in type, as well. This means that any class object or variable of a built-in type can also be defined at any point in a scope. C, of course, would never allow a variable to be defined anywhere except at the beginning of the scope. Generally, you should define variables as close to their point of use as possible, and always initialize them when they are defined. By reducing the duration of the variable's availability within the scope, you are reducing the chance it will be misused in some other part of the scope. In addition, readability is improved because the reader doesn't have to jump back and forth to the beginning of the scope to know the type of a variable. The variables i and j are defined directly inside the for expression (which you cannot do in C). They are then available for use in the for loop. It's a very convenient syntax because the context removes all question about the purpose of i and j, so you don't need to use such ungainly names as i_loop_counter for clarity. The problem is the lifetime of the variables, which was formerly determined by the enclosing scope. This is a situation where a design decision was made from a compiler-writer's view of what is logical because as a programmer you obviously intend i to be used only inside the statement(s) of the for loop. The new Standard C++ specification says that the lifetime of a loop counter defined within the control expression of a for loop lasts until the end of the controlled expression, so the above statements will work. Watch out, though, for local variables that hide variables in the enclosing scope. I find small scopes an indicator of good design. If you have several pages for a single function, perhaps you're trying to do too much with that function. More granular functions are not only more useful, but it's also easier to find bugs. Storage allocation A variable can now be defined at any point in a scope, so it might seem initially that the storage for a variable may not be defined until its point of definition. It's more likely that the compiler will follow the practice in C of allocating all the storage for a block at the opening brace of that block. It doesn't matter because, as a programmer, you can't get the storage (a.k.a. the object) until it has been defined. Although the storage is allocated at the beginning of the block, the constructor call doesn't happen until the sequence point where the object is defined because the identifier isn't available until then. The compiler even checks to make sure you don't put the object definition (and thus the constructor call) where the sequence point only conditionally passes through it, such as in a switch statement or somewhere a goto can jump past it. That object will then be in scope even if the constructor hasn't been called, so the compiler gives an error message. This once again guarantees that an object cannot be created unless it is also initialized. All the storage allocation discussed here happens, of course, on the stack. The storage is allocated by the compiler by moving the stack pointer down (a relative term, which may indicate an increase or decrease of the actual stack pointer value, depending on your machine). Objects can also be allocated on the heap, but that's the subject of Chapter 11. Stash with constructors and destructors The examples from previous chapters have obvious functions that map to constructors and destructors: initialize( ) and cleanup( ). This has nothing to do with the fact that it's nested. How do you create an object on the heap if it has a constructor? There is an easy solution to this problem, the operator new, that we'll look at in Chapter 11, but for now the C approach to dynamic allocation will have to suffice. Aggregate initialization An aggregate is just what it sounds like: a bunch of things clumped together. This definition includes aggregates of mixed types, like structs and classes. An array is an aggregate of a single type. Initializing aggregates can be error-prone and tedious. C++ aggregate initialization makes it much safer. When you create an object that's an aggregate, all you must do is make an assignment, and the initialization will be taken care of by the compiler. This assignment comes in several flavors, depending on the type of aggregate you're dealing with, but in all cases the elements in the assignment must be surrounded by curly braces. Notice this initialization behavior doesn't occur if you define an array without a list of initializers. If you can set your code up so it needs to be changed in only one spot, you reduce the chance of errors during modification. But how do you determine the size of the array? If any of the data members are private, or even if everything's public but there's a constructor, things are different. In the above examples, the initializers are assigned directly to the elements of the aggregate, but constructors are a way of forcing initialization to occur through a formal interface. Here, the constructors must be called to perform the initialization. Any time you have a constructor, whether it's a struct with all members public or a class with private data members, all the initialization must go through the constructor, even if you're using aggregate initialization. Default constructors A default constructor is one that can be called with no arguments. A default constructor is used to create a vanilla object, but it's also very important when the compiler is told to create an object but isn't given any details. The second object in the array wants to be created with no arguments, and that's where the compiler looks for a default constructor. You might think that the default constructor should do some intelligent initialization, like setting all the memory for the object to zero. But it doesn't - that would add extra overhead but be out of the programmer's control. This would mean, for example, that if you compiled C code under C++, the effect would be different. If you want the memory to be initialized to zero, you must do it yourself. The automatic creation of default constructors was not simply a feature to make life easier for new C++ programmers. It's virtually required to aid backward compatibility with existing C code, which is a critical issue in C++. In C, it's not uncommon to create an array of structs. Without the default constructor, this would cause a compile-time error in C++. If you had to modify your C code to recompile it under C++ just because of stylistic issues, you might not bother. When you move C code to C++, you will almost always have new compile-time error messages, but those errors are because of genuine bad C code that the C++ compiler can detect because of its stronger rules. In fact, a good way to find obscure errors in a C program is to run it through a C++ compiler. Summary The seemingly elaborate mechanisms provided by C++ should give you a strong hint about the critical importance placed on initialization and cleanup in the language. As Stroustrup was designing C++, one of the first observations he made about productivity in C was that a very significant portion of programming problems are caused by improper initialization of variables. These kinds of bugs are very hard to find, and similar issues apply to improper cleanup. Aggregate initialization is included in a similar vein - it prevents you from making typical initialization mistakes with aggregates of built-in types and makes your code more succinct. Safety during coding is a big issue in C++. Initialization and cleanup are an important part of this, but you'll also see other safety issues as the book progresses. Exercises 1. Modify the HANDLE.H, HANDLE.CPP, and USEHANDL.CPP files at the end of Chapter 2 to use constructors and destructors. 2. Create a class with a destructor and nondefault constructor, each of which print something to announce their presence. Write code that demonstrates when the constructor and destructor are called. 3. Demonstrate automatic counting and aggregate initialization with an array of objects of the class you created in Exercise 2. Add a member function to that class that prints a message. Calculate the size of the array and move through it, calling your new member function. 4. Create a class without any constructors, and show you can create objects with the default constructor. Now create a nondefault constructor (one with an argument) for the class, and try compiling again. When you create an object (a variable), you give a name to a region of storage. A function is a name for an action. By using names that you make up to describe the system at hand, you create a program that is easier for people to understand and change. It's a lot like writing prose - the goal is to communicate with your readers. A problem arises when mapping the concept of nuance in human language onto a programming language. Often, the same word expresses a number of different meanings, depending on context. That is, a single word has multiple meanings - it's overloaded. This is very useful, especially when it comes to trivial differences. You say wash the shirt, wash the car. It would be silly to be forced to say, shirt_wash the shirt, car_wash the car just so the hearer doesn't have to make any distinction about the action performed. Most human languages are redundant, so even if you miss a few words, you can still determine the meaning. We don't need unique identifiers - we can deduce meaning from context. Most programming languages, however, require that you have a unique identifier for each function. If you have three different types of data you want to print, int, char, and float, you generally have to create three different function names, for example, print_int( ), print_char( ), and print_float( ). This loads extra work on you as you write the program, and on readers as they try to understand it. In C++, another factor forces the overloading of function names: the constructor. Because the constructor's name is predetermined by the name of the class, there can be only one constructor name. But what if you want to create an object in more than one way? For example, suppose you build a class that can initialize itself in a standard way and also by reading information from a file. You need two constructors, one that takes no arguments (the default constructor) and one that takes a character string as an argument, which is the name of the file to initialize the object. Both are constructors, so they must have the same name - the name of the class. Thus function overloading is essential to allow the same function name, the constructor in this case, to be used with different argument types. Although function overloading is a must for constructors, it's a general convenience and can be used with any function, not just class member functions. In addition, function overloading means that if you have two libraries that contain functions of the same name, the chances are they won't conflict as long as the argument lists are different. We'll look at all these factors in detail throughout this chapter. The theme of this chapter is convenient use of function names. Function overloading allows you to use the same name for different functions, but there's a second way to make calling a function more convenient. What if you'd like to call the same function in different ways? When functions have long argument lists, it can become tedious to write and confusing to read the function calls when most of the arguments are the same for all the calls. A very commonly used feature in C++ is called default arguments. A default argument is one the compiler inserts if the person calling a function doesn't specify it. Thus the calls f(hello), f(hi, 1) and f(howdy, 2, 'c') can all be calls to the same function. They could also be calls to three overloaded functions, but when the argument lists are this similar, you'll usually want similar behavior that calls for a single function. Function overloading and default arguments really aren't very complicated. By the time you reach the end of this chapter, you'll understand when to use them and the underlying mechanisms used during compiling and linking to implement them. More mangling In Chapter 1 the concept of name mangling was introduced. The compiler performs this scoping by manufacturing different internal names for the global version of f( ) and X::f( ). In Chapter 1 it was suggested that the names are simply the class name mangled together with the function name, so the internal names the compiler uses might be _f and _X_f. It turns out that function name mangling involves more than the class name. Here's why. Suppose you want to overload two function names void print(char); void print(float); It doesn't matter whether they are both inside a class or at the global scope. The compiler can't generate unique internal identifiers if it uses only the scope of the function names. You'd end up with _print in both cases. The idea of an overloaded function is that you use the same function name, but different argument lists. Thus, for overloading to work the compiler must mangle the names of the argument types with the function name. The above functions, defined at global scope, produce internal names that might look something like _print_char and _print_float. It's worth noting there is no standard for the way names must be mangled by the compiler, so you will see very different results from one compiler to another. That's really all there is to function overloading: You can use the same function name for different functions, as long as the argument lists are different. The compiler mangles the name, the scope, and the argument lists to produce internal names for it and the linker to use. Overloading on return values It's common to wonder why just scopes and argument lists? Why not return values? It seems at first that it would make sense to also mangle the return value with the internal function name. How can the compiler distinguish which call is meant in this case? Possibly worse is the difficulty the reader has in knowing which function call is meant. Overloading solely on return value is a bit too subtle, and thus isn't allowed in C++. Type-safe linkage There is an added benefit to all this name mangling. A particularly sticky problem in C occurs when the user misdeclares a function, or, worse, a function is called without declaring it first, and the compiler infers the function declaration from the way it is called. Sometimes this function declaration is correct, but when it isn't, it can be a very difficult bug to find. Because all functions must be declared before they are used in C++, the opportunity for this problem to pop up is greatly diminished. The compiler refuses to declare a function automatically for you, so it's likely you will include the appropriate header file. Consider the following scenario. Thus, the compilation is successful. In C, the linker would also be successful, but not in C++. Because the compiler mangles the names, the definition becomes something like f_int, whereas the use of the function is f_char. When the linker tries to resolve the reference to f_char, it can find only f_int, and it gives you an error message. This is type-safe linkage. Although the problem doesn't occur all that often, when it does it can be incredibly difficult to find, especially in a large project. This is one of the cases where you can find a difficult error in a C program simply by running it through the C++ compiler. Overloading example Consider the examples we've been looking at so far in this series, modified to use function overloading. As stated earlier, an immediately useful place for overloading is in constructors. The allocation happens the first time you try to add( ) an object and any time the current block of memory is exceeded inside add( ). Default arguments Examine the two constructors for Stash( ). They don't seem all that different, do they? In fact, the first constructor seems to be the special case of the second one with the initial size set to zero. In this situation it seems a bit of a waste of effort to create and maintain two different versions of a similar function. C++ provides a remedy with default arguments. A default argument is a value given in the declaration that the compiler automatically inserts if you don't provide a value in the function call. Now, the two object definitions Stash A(100), B(100, 0); will produce exactly the same results. The identical constructor is called in both cases, but for A, the second argument is automatically substituted by the compiler when it sees the first argument is an int and there is no second argument. The compiler has seen the default argument, so it knows it can still make the function call if it substitutes this second argument, which is what you've told it to do by making it a default. Default arguments are a convenience, as function overloading is a convenience. Both features allow you to use a single name in different situations. The difference is that the compiler is substituting arguments when you don't want to put them in yourself. The preceding example is a good place to use default arguments instead of function overloading; otherwise you end up with two or more functions that have similar signatures and similar behaviors. Obviously, if the functions have very different behaviors, it usually doesn't make sense to use default arguments. There are two rules you must be aware of when using default arguments. First, only trailing arguments may be defaulted. That is, you can't have a default argument followed by a nondefault argument. Second, once you start using default arguments, all the remaining arguments must be defaulted. The compiler must see the default value before it can use it. The calls must still use a placeholder, though: f(1) or f(1,2,3.0). This syntax allows you to put the argument in as a placeholder without using it. The idea is that you might want to change the function definition to use it later, without changing all the function calls. Of course, you can accomplish the same thing by using a named argument, but if you define the argument for the function body without using it, most compilers will give you a warning message, assuming you've made a logical error. By intentionally leaving the argument name out, you suppress this warning. A bit vector class As a further example of function overloading and default arguments, consider the problem of efficiently storing a set of true-false flags. If you have a number of pieces of data that can be expressed as on or off, it may be convenient to store them in an object called a bit vector. Sometimes a bit vector is not a tool to be used by the application developer, but a part of other classes. Sometimes this storage is important, especially if you want to build other classes using this class. So consider instead the following BitVector, which uses a bit for each flag. You can't set any bits in this vector because there are none. First you have to increase the size of the vector with the overloaded bits( ) function. The version with no arguments returns the current size of the vector in bits, and bits(int) changes the size to what is specified in the argument. Thus you both set and read the size using the same function name. Note that there's no restriction on the new size - you can make it smaller as well as larger. The second constructor takes a pointer to an array of unsigned chars, that is, an array of raw bytes. The second argument tells the constructor how many bytes are in the array. If the first argument is zero rather than a valid pointer, the array is initialized to zero. If you don't give a second argument, the default size is eight bytes. You might think you can create a BitVector of size eight bytes and set it to zero by saying BitVector b(0);. This would work if not for the third constructor, which takes a char* as its only argument. The argument 0 could be used in either the second constructor (with the second argument defaulted) or the third constructor. The compiler has no way of knowing which one it should choose, so you'll get an ambiguity error. To successfully create a BitVector this way, you must cast zero to a pointer of the proper type: BitVector b((unsigned char*)0). This is awkward, so you may instead want to create an empty vector with BitVector b and then expand it to the desired size with b.bits(64) to allocate eight bytes. It's important that the compiler distinguish char* and unsigned char* as two distinct data types. If it did not (a problem in the past) then BitVector(unsigned char*, int) (with the second argument defaulted) and BitVector(char*) would look the same when the compiler tried to match the function call. Note that the print( ) function has a default argument for its char* argument. This may look a bit puzzling if you know how the compiler handles string constants. Does the compiler create a new default character string every time you call the function? The answer is no; it creates a single string in a special area reserved for static and global data, and passes the address of that string every time it needs to use it as a default. A string of bits The third constructor for the BitVector takes a pointer to a character string that represents a string of bits. This is a convenient syntax for the user because it allows the vector initialization values to be expressed in the natural form 0110010. The object is created to match the length of the string, and each bit is set or cleared according to the string. The other functions are the all-important set( ), clear( ), and read( ), each of which takes the bit number of interest as an argument. The print( ) function prints a message, which has a default argument of an empty string, and then the bit pattern of the BitVector, again using ones and zeros. Two issues are immediately apparent when implementing the BitVector class. One is that if the number of bits you need doesn't fall on an 8-bit boundary (or whatever word size your machine uses), you must round up to the nearest boundary. The second is the care necessary in selecting the bits of interest. For example, when creating a BitVector using an array of bytes, each byte in the array must be read in from left to right so it will appear the way you expect it in the print( ) function. The second constructor allocates storage and initializes the number of bits, and then it gets a little tricky. The outer for loop indexes through the array of bytes, and the inner for loop indexes through each byte a bit at a time. Notice this is a bitwise AND, and the hex 0x80 (a 1-bit in the highest location) is shifted to the right by offset to create a mask. If the result is nonzero, there is a one in that particular bit position, and the set( ) function is used to set the bit inside the BitVector. It was important to scan the source bytes from left to right so the print( ) function makes sense to the viewer. The third constructor converts from a character string representing a binary sequence of ones and zeroes into a BitVector. The number of bits is taken at face value - the length of the character string. In this case, unlike the second constructor, the bits are scanned in from left to right from the source string. The set( ), clear( ), and read( ) functions follow a nearly identical format. The first three lines are identical in each case: assert( ) that the argument is in range, and create an index into the array of bytes and an offset into the selected byte. Both set( ) and read( ) create their mask the same way: by shifting a bit left into the desired position. But set( ) forces the bit in the array to be set by ORing the appropriate byte with the mask, and read( ) checks the value by ANDing the mask with the byte and seeing if the result is nonzero. Note that set( ), read( ), and clear( ) could be written much more succinctly. The two overloaded bits( ) functions are quite different in their behavior. The first is simply an access function (a function that produces a value based on private data without allowing access to that data) that tells how many bits are in the array. The second uses its argument to calculate the new number of bytes required, realloc( )s the memory (which allocates fresh memory if bytes is zero) and zeroes the additional bits. Note that if you ask for the same number of bits you've already got, this may actually reallocate the memory (depending on the implementation of realloc( )) but it won't hurt anything. The print( ) function puts out the msg string. The Standard C library function puts( ) always adds a new line, so this will result in a new line for the default argument. Then it uses read( ) on each successive bit to print the appropriate character. For easier visual scanning, after each eight bits it prints out a space. Because of the way the second BitVector constructor reads in its array of bytes, the print( ) function will produce results in a familiar form. The set( ) and clear( ) functions are demonstrated. You should be aware that the Standard C++ library contains bits and bitstring classes which are much more complete (and standard) implementations of bit vectors. Summary Both function overloading and default arguments provide a convenience for calling function names. It can seem confusing at times to know which technique to use. The use appears to be the same as the previous scheme. However, there are a number of significant differences that jump out, or at least should make you feel uncomfortable. Inside bits( ) you'll have to do a conditional based on the value of the argument. If you have to look for the default rather than treating it as an ordinary value, that should be a clue that you will end up with two different functions inside one: one version for the normal case, and one for the default. You might as well split it up into two distinct function bodies and let the compiler do the selection. This results in a slight increase in efficiency, because the extra argument isn't passed and the extra code for the conditional isn't executed. The slight efficiency increase for two functions could make a difference if you call the function many times. You do lose something when you use a default argument in this case. First, the default has to be something you wouldn't ordinarily use, -1 in this case. Now you can't tell if a negative number is an accident or a default substitution. Second, there's only one return value with a single function, so the compiler loses the information that was available for the overloaded functions. Now, if you say int i = bv1.set(10); the compiler will accept it and no longer sees something that you, as the class designer, might want, to be an error. And consider the plight of the user, always. Which design will make more sense to users of your class as they peruse the header file? What does a default argument of -1 suggest? Not much. The two separate functions are much clearer because one takes a value and doesn't return anything and the other doesn't take a value but returns something. Even without documentation, it's far easier to guess what the two different functions do. As a guideline, you shouldn't use a default argument as a flag upon which to conditionally execute code. You should instead break the function into two or more overloaded functions if you can. A default argument should be a value you would ordinarily put in that position. It's a value that is more likely to occur than all the rest, so users can generally ignore it or use it only if they want to change it from the default value. The default argument is included to make function calls easier, especially when those functions have many arguments with typical values. Not only is it much easier to write the calls, it's easier to read them, especially if the class creator can order the arguments so the least-modified defaults appear latest in the list. An especially important use of default arguments is when you start out with a function with a set of arguments, and after it's been used for a while you discover you need to add arguments. By defaulting all the new arguments, you ensure that all client code using the previous interface is not disturbed. Exercises 1. Create a message class with a constructor that takes a single char* with a default value. Create a private member char*, and assume the constructor will be passed a static quoted string; simply assign the argument pointer to your internal pointer. Create two overloaded member functions called print( ): one that takes no arguments and simply prints the message stored in the object, and one that takes a char* argument, which it prints in addition to the internal message. Does it make sense to use this approach rather than the one used for the constructor? 2. Determine how to generate assembly output with your compiler, and run experiments to deduce the name-mangling scheme. 3. Modify STASH4.H and STASH4.CPP to use default arguments in the constructor. Test the constructor by making two different versions of a Stash object. 4. Compare the execution speed of the Flags class versus the BitVector class. To ensure there's no confusion about efficiency, first remove the index, offset, and mask clarification definitions in set( ), clear( ) and read( ) by combining them into a single statement that performs the appropriate action. Give the constructor an argument that is the size of the storage, and put a default of 100 on that argument. This provides safety and control in a C++ programming project. Since its origin, it has taken on a number of different purposes. In the meantime it trickled back into the C language where its meaning was changed. All this can seem a bit confusing at first, and in this chapter you'll learn when, why, and how to use the const keyword. At the end there's a discussion of volatile, which is a near cousin to const (because they both concern change) and has identical syntax. The first motivation for const seems to have been to eliminate the use of preprocessor #defines for value substitution. It has since been put to use for pointers, function arguments, and return types, and class objects and member functions. All of these have slightly different but conceptually compatible meanings and will be looked at in separate sections. Value substitution When programming in C, the preprocessor is liberally used to create macros and to substitute values. Because the preprocessor simply does text replacement and has no concept nor facility for type checking, preprocessor value substitution introduces subtle problems that can be avoided in C++ by using const values. It's very important to use value substitution instead of so-called magic numbers to support code maintenance. Most of the time, BUFSIZE will behave like an ordinary variable, but not all the time. In addition, there's no type information. This can hide bugs that are very difficult to find. C++ uses const to eliminate these problems by bringing value substitution into the domain of the compiler. You should always use const instead of #define value substitution. This way, you can place the definition for a const in a single place and distribute it to a translation unit by including the header file. A const in C++ defaults to internal linkage; that is, it is visible only within the file where it is defined and cannot be seen at link time by other translation units. When the const is used, it is folded in at compile time. Of course, this goal of never allocating storage for a const cannot always be achieved, especially with complicated structures. In these cases, the compiler creates storage, which prevents constant folding. This is why const must default to internal linkage, that is, linkage only within that particular translation unit; otherwise, linker errors would occur with complicated consts because they allocate storage in multiple CPP files. The linker sees the same definition in multiple object files, and complains. However, a const defaults to internal linkage, so the linker doesn't try to link those definitions across translation units, and there are no collisions. With built-in types, which are used in the majority of cases involving constant expressions, the compiler can always perform constant folding. Safety consts The use of const is not limited to replacing #defines in constant expressions. However, because i is a const, the calculated value for j still comes from a constant expression and is itself a compile-time constant. The very next line requires the address of j and therefore forces the compiler to allocate storage for j. Yet this doesn't prevent the use of j in the determination of the size of buf because the compiler knows j is const and that the value is valid even if storage was allocated to hold that value at some point in the program. In main( ), you see a different kind of const in the identifier c because the value cannot be known at compile time. This means storage is required, and the compiler doesn't attempt to keep anything in its symbol table (the same behavior as in C). The initialization must still happen at the point of definition, and once the initialization occurs, the value cannot be changed. You can see that c2 is calculated from c and also that scoping works for consts as it does for any other type - yet another improvement over the use of #define. As a matter of practice, if you think a value shouldn't change, you should make it a const. This not only provides insurance against inadvertent changes, it also allows the compiler to generate more efficient code by eliminating storage and memory reads. Aggregates It's possible to use const for aggregates, but you're virtually assured that the compiler will not be sophisticated enough to keep an aggregate in its symbol table, so storage will be allocated. In these situations, const means a piece of storage that cannot be changed. However, the value cannot be used at compile time because the compiler is not required to know the contents of storage at compile time. In both of the illegal definitions, the compiler complains because it cannot find a constant expression in the array definition. Differences with C Constants were introduced in early versions of C++ while the Standard C specification was still being finished. It was then seen as a good idea and included in C. But somehow, const in C came to mean an ordinary variable that cannot be changed. In C, it always occupies storage and its name is global. The C compiler cannot treat a const as a compile-time constant. Because bufsize occupies storage somewhere, the C compiler cannot know the value at compile time. You can optionally say const bufsize; in C, but not in C++, and the C compiler accepts it as a declaration indicating there is storage allocated elsewhere. Because C defaults to external linkage for consts, this makes sense. The C approach to const is not very useful, and if you want to use a named value inside a constant expression (one that must be evaluated at compile time), C almost forces you to use #define in the preprocessor. Pointers Pointers can be made const. The compiler will still endeavor to prevent storage allocation and do constant folding when dealing with const pointers, but these features seem less useful in this case. More importantly, the compiler will tell you if you attempt changes using such a pointer later in your code, which adds a great deal of safety. When using const with pointers, you have two options: const can be applied to what the pointer is pointing to, or the const can be applied to the address stored in the pointer itself. The syntax for these is a little confusing at first but becomes comfortable with practice. Pointer to const The trick with a pointer definition, as with any complicated definition, is to read it starting at the identifier and working your way out. Here's the mildly confusing part. The fact that these two definitions are the same is the confusing point; to prevent this confusion on the part of your reader, you should probably stick to the first form. Some people argue that the second form is more consistent because the const is always placed to the right of what it modifies. You'll have to decide which is clearer for your particular coding style. Formatting This book makes a point of only putting one pointer definition on a line, and initializing each pointer at the point of definition whenever possible. Because of this, the formatting style of attaching the '*' to the data type is possible: int* u = andw; as if int* were a discrete type unto itself. This makes the code easier to understand, but unfortunately that's not actually the way things work. The '*' in fact binds to the identifier, not the type. It can be placed anywhere between the type name and the identifier. So you can do this: int* u = andw, v = 0; which creates an int* u, as before, and a nonpointer int v. Because readers often find this confusing, it is best to follow the form shown in this book. Assignment and type checking C++ is very particular about type checking, and this extends to pointer assignments. You can assign the address of a non-const object to a const pointer because you're simply promising not to change something that is OK to change. However, you can't assign the address of a const object to a non-const pointer because then you're saying you might change the object via the pointer. Of course, you can always use a cast to force such an assignment, but this is bad programming practice because you are then breaking the constness of the object, along with any safety promised by the const. String literals The place where strict constness is not enforced is with string literals. You can say char* cp = howdy; and the compiler will accept it without complaint. This is technically an error because a string literal (howdy in this case) is created by the compiler as a constant string, and the result of the quoted string is its starting address in memory. So string literals are actually constant strings. Of course, the compiler lets you get away with treating them as non-const because there's so much existing C code that relies on this. However, if you try to change the values in a string literal, the behavior is undefined, although it will probably work on many machines. Function arguments and return values The use of const to specify function arguments and return values is another place where the concept of constants can be confusing. If you are passing objects by value, specifying const has no meaning to the client (it means that the passed argument cannot be modified inside the function). If you are returning an object of a user-defined type by value as a const, it means the returned value cannot be modified. If you are passing and returning addresses, const is a promise that the destination of the address will not be changed. You're making a promise that the original value of the variable will not be changed by the function x( ). However, because the argument is passed by value, you immediately make a copy of the original variable, so the promise to the client is implicitly kept. Inside the function, the const takes on meaning: the argument cannot be changed. So it's really a tool for the creator of the function, and not the caller. To avoid confusion to the caller, you can make the argument a const inside the function, rather than in the argument list. You could do this with a pointer, but a nicer syntax is achieved with the reference, a subject that will be fully developed in Chapter 9. Briefly, a reference is like a constant pointer that is automatically dereferenced, so it has the effect of being an alias to an object. To create a reference, you use the and in the definition. Returning by const value A similar truth holds for the return value. If you return by value from a function, as a const const int g(); you are promising that the original variable (inside the function frame) will not be modified. And again, because you're returning it by value, it's copied so the original value is automatically not modified. At first, this can make the specification of const seem meaningless. Returning by value as a const becomes important when you're dealing with user-defined types. If a function returns a class object by value as a const, the return value of that function cannot be an lvalue (that is, it cannot be assigned to or otherwise modified). Only the non-const return value can be used as an lvalue. Thus, it's important to use const when returning an object by value if you want to prevent its use as an lvalue. The reason const has no meaning when you're returning a built-in type by value is that the compiler already prevents it from being an lvalue (because it's always a value, and not a variable). Only when you're returning objects of user-defined types by value does it become an issue. The function f7( ) takes its argument as a non-const reference (an additional way of handling addresses in C++ which is the subject of Chapter 9). This is effectively the same as taking a non-const pointer; it's just that the syntax is different. Temporaries Sometimes, during the evaluation of an expression, the compiler must create temporary objects. These are objects like any other: they require storage and they must be constructed and destroyed. The difference is that you never see them - the compiler is responsible for deciding that they're needed and the details of their existence. But there is one thing about temporaries: they're automatically const. Because you usually won't be able to get your hands on a temporary object, telling it to do something that will change that temporary is almost certainly a mistake because you won't be able to use that information. By making all temporaries automatically const, the compiler informs you when you make that mistake. The way the constness of class objects is preserved is shown later in the chapter. Passing and returning addresses If you pass or return a pointer (or a reference), it's possible for the user to take the pointer and modify the original value. If you make the pointer a const, you prevent this from happening, which may be an important factor. In fact, whenever you're passing an address into a function, you should make it a const if at all possible. If you don't, you're excluding the possibility of using that function with a pointer to a const. The choice of whether to return a pointer to a const depends on what you want to allow your user to do with it. Inside u( ) you can see that attempting to modify the destination of the const pointer is illegal, but you can of course copy the information out into a non-const variable. The compiler also prevents you from creating a non-const pointer using the address stored inside a const pointer. The functions v( ) and w( ) test return value semantics. This statement actually produces the address of the string literal, after the compiler creates it and stores it in the static storage area. As mentioned earlier, this string is technically a constant, which is properly expressed by the return value of v( ). The return value of w( ) requires that both the pointer and what it points to be a const. As with v( ), the value returned by w( ) is valid after the function returns only because it is static. You never want to return pointers to local stack variables because they will be invalid after the function returns and the stack is cleaned up. In main( ), the functions are tested with various arguments. You can see that t( ) will accept a non-const pointer argument, but if you try to pass it a pointer to a const, there's no promise that t( ) will leave the pointer's destination alone, so the compiler gives you an error message. Thus, a function that takes a const pointer is more general than one that does not. As expected, the return value of v( ) can be assigned only to a const pointer. Once again, because the value (which is the address contained in the pointer) is being copied, the promise that the original variable is untouched is automatically kept. Thus, the second const in const int* const is only meaningful when you try to use it as an lvalue, in which case the compiler prevents you. Standard argument passing In C it's very common to pass by value, and when you want to pass an address your only choice is to use a pointer. However, neither of these approaches is preferred in C++. Instead, your first choice when passing an argument is to pass by reference, and by const reference at that. To the client programmer, the syntax is identical to that of passing by value, so there's no confusion about pointers - they don't even have to think about the problem. So passing by reference produces a new situation that never occurs in C: a temporary, which is always const, can have its address passed to a function. This is why, to allow temporaries to be passed to functions by reference the argument must be a const reference. That means when you immediately take the return value of f( ) and pass it to another function as in the calls to g1( ) and g2( ), a temporary is created and that temporary is const. Thus, the call in g1( ) is an error because g1( ) doesn't take a const reference, but the call to g2( ) is OK. Classes This section shows the two ways to use const with classes. You may want to create a local const in a class to use inside constant expressions that will be evaluated at compile time. However, the meaning of const is different inside classes, so you must use an alternate technique with enumerations to achieve the same effect. You can also make a class object const (and as you've just seen, the compiler always makes temporary class objects const). But preserving the constness of a class object is more complex. The compiler can ensure the constness of a built-in type but it cannot monitor the intricacies of a class. To guarantee the constness of a class object, the const member function is introduced: Only a const member function may be called for a const object. The typical example is when you're creating an array inside a class and you want to use a const instead of a #define to establish the array size and to use in calculations involving the array. The array size is something you'd like to keep hidden inside the class, so if you used a name like size, for example, you could use that name in another class without a clash. The preprocessor treats all #defines as global from the point they are defined, so this will not achieve the desired effect. Initially, you probably assume that the logical choice is to place a const inside the class. This doesn't produce the desired result. Inside a class, const partially reverts to its meaning in C. It allocates storage within each class object and represents a value that is initialized once and then cannot change. The use of const inside a class means This is constant for the lifetime of the object. However, each different object may contain a different value for that constant. Thus, when you create a const inside a class, you cannot give it an initial value. This initialization must occur in the constructor, of course, but in a special place in the constructor. Because a const must be initialized at the point it is created, inside the main body of the constructor the const must already be initialized. Otherwise you're left with the choice of waiting until some point later in the constructor body, which means the const would be un-initialized for a while. Also, there's nothing to keep you from changing the value of the const at various places in the constructor body. The constructor initializer list The special initialization point is called the constructor initializer list, and it was originally developed for use in inheritance (an object-oriented subject of a later chapter). This is to remind you that the initialization in the list occurs before any of the main constructor code is executed. It made sense to extend this constructor for built-in types (which simply means assignment) to the general case. Now you can say float pi(3.14159); It's often useful to encapsulate a built-in type inside a class to guarantee initialization with the constructor. Many compilers easily optimize this to a very fast process. Compile-time constants in classes Because storage is allocated in the class object, the compiler cannot know what the contents of the const are, so it cannot be used as a compile-time constant. This means that, for constant expressions inside classes, const becomes as useless as it is in C. A common solution is to use an untagged enum with no instances. An enumeration must have all its values established at compile time, it's local to the class, and its values are available for constant expressions. However, it also prevents you from doing anything that will change the objects contained by StringStack. Of course, not all containers are designed with this restriction. Although you'll often see the enum technique in legacy code, C++ also has the static const which produces a more flexible compile-time constant inside a class. This is described in Chapter 8. Type checking for enumerations C's enumerations are fairly primitive, simply associating integral values with names, but providing no type checking. In C++, as you may have come to expect by now, the concept of type is fundamental, and this is true with enumerations. When you create a named enumeration, you effectively create a new type just as you do with a class: The name of your enumeration becomes a reserved word for the duration of that translation unit. In addition, there's stricter type checking for enumerations in C++ than in C. You'll notice this in particular if you have an instance of an enumeration color called a. In C you can say a++ but in C++ you can't. This is because incrementing an enumeration is performing two type conversions, one of them legal in C++ and one of them illegal. First, the value of the enumeration is implicitly cast from a color to an int, then the value is incremented, then the int is cast back into a color. In C++ this isn't allowed, because color is a distinct type and not equivalent to an int. This makes sense because how do you know the increment of blue will even be in the list of colors? If you want to increment a color, then it should be a class (with an increment operation) and not an enum. Any time you write code that assumes an implicit conversion to an enum type, the compiler will flag this inherently dangerous activity. Unions have similar additional type checking. What does this mean? To understand, you must first grasp the concept of const objects. A const object is defined the same for a user-defined type as a built-in type. For example: const int i = 1; const blob B(2); Here, B is a const object of type blob. Its constructor is called with an argument of two. For the compiler to enforce constness, it must ensure that no data members of the object are changed during the object's lifetime. It can easily ensure that no public data is modified, but how is it to know which member functions will change the data and which ones are safe for a const object? If you declare a member function const, you tell the compiler the function can be called for a const object. A member function that is not specifically declared const is treated as one that will modify data members in an object, and the compiler will not allow you to call it for a const object. It doesn't stop there, however. Just claiming a function is const inside a class definition doesn't guarantee the member function definition will act that way, so the compiler forces you to reiterate the const specification when defining the function. Thus, any member function you declare const is guaranteed to behave that way in the definition. Preceding the function declaration with const means the return value is const, so that isn't the proper syntax. You must place the const specifier after the argument list. Any function that doesn't modify member data should be declared as const, so it can be used with const objects. The quote( ) member function also cannot be const because it modifies the data member lastquote in the return statement. However, Lastquote( ) makes no modifications, and so it can be const and can be safely called for the const object cq. This is sometimes referred to as the difference between bitwise const and memberwise const. Bitwise const means that every bit in the object is permanent, so a bit image of the object will never change. Memberwise const means that, although the entire object is conceptually constant, there may be changes on a member-by-member basis. However, if the compiler is told that an object is const, it will jealously guard that object. There are two ways to change a data member inside a const member function. The first approach is the historical one and is called casting away constness. It is performed in a rather odd fashion. You take this (the keyword that produces the address of the current object) and you cast it to a pointer to an object of the current type. It would seem that this is already such a pointer, but it's a const pointer, so by casting it to an ordinary pointer, you remove the constness for that operation. The problem is that this lack of constness is hidden away in a member function of an object, so the user has no clue that it's happening unless she has access to the source code (and actually goes looking for it). ROMability If an object is defined as const, it is a candidate to be placed in read-only memory (ROM), which is often an important consideration in embedded systems programming. Simply making an object const, however, is not enough - the requirements for ROMability are much more strict. Of course, the object must be bitwise-const, rather than memberwise-const. This is easy to see if memberwise constness is implemented only through the mutable keyword, but probably not detectable by the compiler if constness is cast away inside a const member function. In addition, 6. The class or struct must have no user-defined constructors or destructor. 7. There can be no base classes (covered in the future chapter on inheritance) or member objects with user-defined constructors or destructors. The effect of a write operation on any part of a const object of a ROMable type is undefined. Although a suitably formed object may be placed in ROM, no objects are ever required to be placed in ROM. If the compiler says, I read the data into a register earlier, and I haven't touched that register, normally it wouldn't need to read the data again. But if the data is volatile, the compiler cannot make such an assumption because the data may have been changed by another process, and it must reread the data rather than optimizing the code. You can create volatile objects just as you create const objects. You can also create const volatile objects, which can't be changed by the programmer but instead change through some outside agency. You can call only volatile member functions for volatile objects. The reason that isr( ) can't actually be used as an interrupt service routine is that in a member function, the address of the current object (this) must be secretly passed, and an ISR generally wants no arguments at all. To solve this problem, you can make isr( ) a static member function, a subject covered in a future chapter. The syntax of volatile is identical to const, so discussions of the two are often treated together. To indicate the choice of either one, the two are referred to in combination as the c-v qualifier. Summary The const keyword gives you the ability to define objects, function arguments and return values, and member functions as constants, and to eliminate the preprocessor for value substitution without losing any preprocessor benefits. All this provides a significant additional form of type checking and safety in your programming. The use of so-called const correctness (the use of const anywhere you possibly can) has been a lifesaver for projects. Although you can ignore const and continue to use old C coding practices, it's there to help you. Chapters 9 and 10 begin using references heavily, and there you'll see even more about how critical it is to use const with function arguments. Exercises 1. Create a class called bird that can fly( ) and a class rock that can't. Create a rock object, take its address, and assign that to a void*. Now take the void*, assign it to a bird*, and call fly( ) through that pointer. Is it clear why C's permission to openly assign via a void* is a hole in the language? 2. Create a class containing a const member that you initialize in the constructor initializer list and an untagged enumeration that you use to determine an array size. 3. Create a class with both const and non-const member functions. Create const and non-const objects of this class, and try calling the different types of member functions for the different types of objects. 4. Create a function that takes an argument by value as a const; then try to change that argument in the function body. 5. Prove to yourself that the C and C++ compilers really do treat constants differently. Create a global const and use it in a constant expression; then compile it under both C and C++. 9: Inline functions One of the important features C++ inherits from C is efficiency. If the efficiency of C++ were dramatically less than C, there would be a significant contingent of programmers who couldn't justify its use. In C, one of the ways to preserve efficiency is through the use of macros, which allow you to make what looks like a function call without the normal overhead of the function call. All the work is performed by the preprocessor, so you have the convenience and readability of a function call but it doesn't cost you anything. There are two problems with the use of preprocessor macros in C++. The first is also true with C: A macro looks like a function call, but doesn't always act like one. This can bury difficult-to-find bugs. The second problem is specific to C++: The preprocessor has no permission to access private data. This means preprocessor macros are virtually useless as class member functions. To retain the efficiency of the preprocessor macro, but to add the safety and class scoping of true functions, C++ has the inline function. In this chapter, we'll look at the problems of preprocessor macros in C++, how these problems are solved with inline functions, and guidelines and insights on the way inlines work. Preprocessor pitfalls The key to the problems of preprocessor macros is that you can be fooled into thinking that the behavior of the preprocessor is the same as the behavior of the compiler. Of course, it was intended that a macro look and act like a function call, so it's quite easy to fall into this fiction. The difficulties begin when the subtle differences appear. When this gap is removed, you can actually call the macro with the gap f (1) and it will still expand properly, to (1 + 1) The above example is fairly trivial and the problem will make itself evident right away. The real difficulties occur when using expressions as arguments in macro calls. There are two problems. The first is that expressions may expand inside the macro so that their evaluation precedence is different from what you expect. Once you discover the problem (and as a general practice when creating preprocessor macros) you can solve it by putting parentheses around everything in the macro definition. So you can easily begin to think that it works with all expressions, including those using bitwise logical operators. The preceding problem can be solved with careful programming practice: Parenthesize everything in a macro. The second difficulty is more subtle. Unlike a normal function, every time you use an argument in a macro, that argument is evaluated. As long as the macro is called only with ordinary variables, this evaluation is benign, but if the evaluation of an argument has side effects, then the results can be surprising and will definitely not mimic function behavior. But as soon as you relax and start believing it is a real function, the problems start. However, when the number is within the band, both conditionals are tested, which results in two increments. The result is produced by evaluating the argument again, which results in a third increment. Once the number gets out of the band, both conditionals are still tested so you get two increments. The side effects are different, depending on the argument. This is clearly not the kind of behavior you want from a macro that looks like a function call. In this case, the obvious solution is to make it a true function, which of course adds the extra overhead and may reduce efficiency if you call that function a lot. Unfortunately, the problem may not always be so obvious, and you can unknowingly get a library that contains functions and macros mixed together, so a problem like this can hide some very difficult-to-find bugs. For example, the putc( ) macro in STDIO.H may evaluate its second argument twice. This is specified in Standard C. In addition, there would be no indication of which object you were referring to. There is simply no way to express class scope in a macro. Without some alternative to preprocessor macros, programmers will be tempted to make some data members public for the sake of efficiency, thus exposing the underlying implementation and preventing changes in that implementation. Inline functions In solving the C++ problem of a macro with access to private class members, all the problems associated with preprocessor macros were eliminated. This was done by bringing macros under the control of the compiler, where they belong. In C++, the concept of a macro is implemented as an inline function, which is a true function in every sense. Any behavior you expect from an ordinary function, you get from an inline function. The only difference is that an inline function is expanded in place, like a preprocessor macro, so the overhead of the function call is eliminated. Thus, you should (almost) never use macros, only inline functions. Any function defined within a class body is automatically inline, but you can also make a nonclass function inline by preceding it with the inline keyword. However, for it to have any effect, you must include the function body with the declaration; otherwise the compiler will treat it as an ordinary function declaration. Thus, inline int PlusOne(int x); has no effect at all other than declaring the function (which may or may not get an inline definition sometime later). Also, if you try to write the above as a preprocessor macro, you get an unwanted side effect. You'll almost always want to put inline definitions in a header file. When the compiler sees such a definition, it puts the function type (signature + return value) and the function body in its symbol table. When you use the function, the compiler checks to ensure the call is correct and the return value is being used correctly, and then substitutes the function body for the function call, thus eliminating the overhead. The inline code does occupy space, but if the function is small, this can actually take less space than the code generated to do an ordinary function call (pushing arguments on the stack and doing the CALL). An inline function in a header file defaults to internal linkage - that is, it is static and can only be seen in translation units where it is included. Thus, as long as they aren't declared in the same translation unit, there will be no clash at link time between an inline function and a global function with the same signature. Inlines inside classes To define an inline function, you must ordinarily precede the function definition with the inline keyword. However, this is not necessary inside a class definition. Any function you define inside a class definition is automatically an inline. Keep in mind, however, that the idea of an inline is to reduce the overhead of a function call. If the function body is large, chances are you'll spend a much larger percentage of your time inside the body versus going in and out of the function, so the gains will be small. But inlining a big function will cause that code to be duplicated everywhere the function is called, producing code bloat with little or no speed benefit. Access functions One of the most important uses of inlines inside classes is the access function. This is a small function that allows you to read or change part of the state of an object - that is, an internal variable or variables. All the access to the private data members can be controlled through the member function interface. In addition, access is remarkably efficient. Consider the read( ), for example. Without inlines, the code generated for the call to read( ) would include pushing this on the stack and making an assembly language CALL. With most machines, the size of this code would be larger than the code created by the inline, and the execution time would certainly be longer. Without inline functions, an efficiency-conscious class designer will be tempted to simply make i a public member, eliminating the overhead by allowing the user to directly access i. From a design standpoint, this is disastrous because i then becomes part of the public interface, which means the class designer can never change it. You're stuck with an int called i. This is a problem because you may learn sometime later that it would be much more useful to represent the state information as a float rather than an int, but because int i is part of the public interface, you can't change it. Accessors and mutators Some people further divide the concept of access functions into accessors (to read state information from an object) and mutators (to change the state of an object). In addition, function overloading may be used to provide the same function name for both the accessor and mutator; how you call the function determines whether you're reading or modifying state information. Of course, accessors and mutators don't have to be simple pipelines to an internal variable. Sometimes they can perform some sort of calculation. The two private functions updateLocal( ) and updateAscii( ) check the flags and conditionally perform the update. The constructor calls the mark( ) function (which the user can also call to force the object to represent the current time), and this clears the two flags to indicate that the local time and ASCII representation are now invalid. The ascii( ) function calls updateAscii( ), which copies the result of the Standard C library function asctime( ) into a local buffer because asctime( ) uses a static data area that is overwritten if the function is called elsewhere. The return value is the address of this local buffer. In the functions starting with DaylightSavings( ), all use the updateLocal( ) function, which causes the composite inline to be fairly large. This doesn't seem worthwhile, especially considering you probably won't call the functions very much. However, this doesn't mean all the functions should be made out of line. If you leave updateLocal( ) as an inline, its code will be duplicated in all the out-of-line functions, eliminating the extra overhead. These are used to show starting, ending, and elapsed times. Inlines and the compiler To understand when inlining is effective, it's helpful to understand what the compiler does when it encounters an inline. As with any function, the compiler holds the function type (that is, the function prototype including the name and argument types, in combination with the function return value) in its symbol table. In addition, when the compiler sees the inline function body and the function body parses without error, the code for the function body is also brought into the symbol table. Whether the code is stored in source form or as compiled assembly instructions is up to the compiler. This, of course, is exactly what the compiler does for any function and is markedly different from what the preprocessor does because the preprocessor cannot check types or make conversions. If all the function type information fits the context of the call, then the inline code is substituted directly for the function call, eliminating the call overhead. Also, if the inline is a member function, the address of the object (this) is put in the appropriate place(s), which of course is another thing the preprocessor is unable to perform. Limitations There are two situations when the compiler cannot perform inlining. In these cases, it simply reverts to the ordinary form of a function by taking the inline definition and creating storage for the function just as it does for a non-inline. If it must do this in multiple translation units (which would normally cause a multiple definition error), the linker is told to ignore the multiple definitions. The compiler cannot perform inlining if the function is too complicated. This depends upon the particular compiler, but at the point most compilers give up, the inline probably wouldn't gain you any efficiency. Generally, any sort of looping is considered too complicated to expand as an inline, and if you think about it, looping probably entails much more time inside the function than embodied in the calling overhead. And remember, every time you call a big inline function, the entire function body is inserted in place of each call, so you can easily get code bloat without any noticeable performance improvement. Some of the examples in this book may exceed reasonable inline sizes in favor of conserving screen real estate. The compiler also cannot perform inlining if the address of the function is taken, implicitly or explicitly. If the compiler must produce an address, then it will allocate storage for the function code and use the resulting address. However, where an address is not required, the compiler will probably still inline the code. It is important to understand that an inline is just a suggestion to the compiler; the compiler is not forced to inline anything at all. A good compiler will inline small, simple functions while intelligently ignoring inlines that are too complicated. This will give you the results you want - the true semantics of a function call with the efficiency of a macro. Order of evaluation If you're imagining what the compiler is doing to implement inlines, you can confuse yourself into thinking there are more limitations than actually exist. This works because the language definition states that no inline functions in a class shall be evaluated until the closing brace of the class declaration. Of course, if g( ) in turn called f( ), you'd end up with a set of recursive calls, which are too complicated for the compiler to inline. Both constructors and destructors may have hidden activities, because the class can contain subobjects whose constructors and destructors must be called. These sub-objects may be member objects, or they may exist because of inheritance (which hasn't been introduced yet). The constructors and destructors for the member objects Q, R, and S are being called automatically, and those constructors and destructors are also inline, so the difference is significant from normal member functions. This doesn't necessarily mean that you should always make constructor and destructor definitions out-of-line. When you're making an initial sketch of a program by quickly writing code, it's often more convenient to use inlines. However, if you're concerned about efficiency, it's a place to look. Reducing clutter In a book like this, the simplicity and terseness of putting inline definitions inside classes is very useful because more fits on a page or screen (in a seminar). However, Dan Saks33 has pointed out that in a real project this has the effect of needlessly cluttering the class interface and thereby making the class harder to use. He refers to member functions defined within classes using the Latin in situ (in place) and maintains that all definitions should be placed outside the class to keep the interface clean. Optimization, he argues, is a separate issue. If you want to optimize, use the inline keyword. Using this approach, the earlier RECTANGL.CPP example (page Error! In situ functions require more work and have greater potential for errors. Another argument for this approach is that you can always produce a consistent formatting style for function definitions, something that doesn't always occur with in situ functions. Preprocessor features Earlier, I said you almost always want to use inline functions instead of preprocessor macros. The exceptions are when you need to use three special features in the Standard C preprocessor (which is, by inheritance, the C++ preprocessor): stringizing, string concatenation, and token pasting. These two features are exceptionally useful when writing debug code. The solution is to replace the semicolon with a comma in the macro. Token pasting Token pasting is very useful when you are manufacturing code. It allows you to take two identifiers and paste them together to automatically create a new identifier. Not only is it easier to read, it can eliminate coding errors and make maintenance easier. Notice, however, the use of all upper-case characters in the name of the macro. This is a helpful practice because it tells the reader this is a macro and not a function, so if there are problems, it acts as a little reminder. Improved error checking It's convenient to improve the error checking for the rest of the book; with inline functions you can simply include the file and not worry about what to link. Up until now, the assert( ) macro has been used for error checking, but it's really for debugging and should be replaced with something that provides useful information at run-time. In addition, exceptions (presented in Chapter 16) provide a much more effective way of handling many kinds of errors - especially those that you'd like to recover from, instead of just halting the program. The conditions described in this section, however, are ones which prevent the continuation of the program, such as if the user doesn't provide enough command-line arguments or a file cannot be opened. Inline functions are convenient here because they allow everything to be placed in a header file, which simplifies the process of using the package. You just include the header file and you don't need to worry about linking. The following header file will be placed in the book's root directory so it's easily accessed from all chapters. Note the use of local using namespace std declarations within each function. This is because some compilers at the time of this writing incorrectly did not include the C standard library functions in namespace std, so explicit qualification would cause a compile-time error. The local declaration allows require.h to work with both correct and incorrect libraries. It's not terribly unsafe, but it's a road best avoided. Note that, once again, a macro looks like a function but behaves differently: it's actually creating an object (in) whose scope persists beyond the macro. You may understand this, but for new programmers and code maintainers it's just one more thing they have to puzzle out. C++ is complicated enough without adding to the confusion, so try to talk yourself out of using macros whenever you can. Summary It's critical that you be able to hide the underlying implementation of a class because you may want to change that implementation sometime later. You'll do this for efficiency, or because you get a better understanding of the problem, or because some new class becomes available that you want to use in the implementation. Anything that jeopardizes the privacy of the underlying implementation reduces the flexibility of the language. Thus, the inline function is very important because it virtually eliminates the need for preprocessor macros and their attendant problems. With inlines, member functions can be as efficient as preprocessor macros. The inline function can be overused in class definitions, of course. The programmer is tempted to do so because it's easier, so it will happen. However, it's not that big an issue because later, when looking for size reductions, you can always move the functions out of line with no effect on their functionality. The development guideline should be First make it work, then optimize it. Exercises 1. Take Exercise 2 from Chapter 6, and add an inline constructor, and an inline member function called print( ) to print out all the values in the array. 2. Take the NESTFRND.CPP example from Chapter 2 and replace all the member functions with inlines. Make them non-in situ inline functions. Also change the initialize( ) functions to constructors. 3. Take the NL.CPP example from Chapter 5 and turn nl into an inline function in its own header file. 4. Create a class A with a default constructor that announces itself. Now make a new class B and put an object of A as a member of B, and give B an inline constructor. Create an array of B objects and see what happens. 5. Create a large quantity of the objects from Exercise 4, and use the Time class to time the difference between a non-inline constructor and an inline constructor. C++ allows you a great deal of control over both the creation and visibility of names, where storage for those names is placed, and linkage for names. The static keyword was overloaded in C before people knew what the term overload meant, and C++ has added yet another meaning. The underlying concept with all uses of static seems to be something that holds its position (like static electricity), whether that means a physical location in memory or visibility within a file. In this chapter, you'll learn how static controls storage and visibility, and an improved way to control access to names via C++'s namespace feature. You'll also find out how to use functions that were written and compiled in C. This is the concept of static storage. 2. Local to a particular translation unit (and class scope in C++, as you will see later). Here, static controls the visibility of a name, so that name cannot be seen outside the translation unit or class. This also describes the concept of linkage, which determines what names the linker will see. This section will look at the above meanings of static as they were inherited from C. If there is an initializer for the variable, the initialization is performed each time that sequence point is passed. Sometimes, however, you want to retain a value between function calls. You could accomplish this by making a global variable, but that variable would not be under the sole control of the function. C and C++ allow you to create a static object inside a function; the storage for this object is not on the stack but instead in the program's static storage area. This object is initialized once the first time the function is called and then retains its value between function invocations. When you call onechar( ) with a char* argument, s is assigned to that argument, and the first character of the string is returned. Each subsequent call to onechar( ) without an argument produces the default value of zero for string, which indicates to the function that you are still extracting characters from the previously initialized value of s. The function will continue to produce characters until it reaches the null terminator of the string, at which point it stops incrementing the pointer so it doesn't overrun the end of the string. But what happens if you call onechar( ) with no arguments and without previously initializing the value of s? So in onechar( ), the first time the function is called, s is zero. In this case, the if(!s) conditional will catch it. The above initialization for s is very simple, but initialization for static objects (like all other objects) can be arbitrary expressions involving constants and previously declared variables and functions. However, assignment to zero has meaning only for built-in types; user-defined types must be initialized with constructor calls. Thus, if you don't specify constructor arguments when you define the static object, the class must have a default constructor. This construction occurs the first time control passes through the definition, and only the first time. This means that it can be dangerous to call exit( ) inside a destructor because you can end up with infinite recursion. Static object destructors are not called if you exit the program using the Standard C library function abort( ). You can specify actions to take place when leaving main( ) (or calling exit( )) by using the Standard C library function atexit( ). In this case, the functions registered by atexit( ) may be called before the destructors for any objects constructed before leaving main( ) (or calling exit( )). Destruction of static objects occurs in the reverse order of initialization. However, only objects that have been constructed are destroyed. Fortunately, the programming system keeps track of initialization order and the objects that have been constructed. Global objects are always constructed before main( ) is entered, so this last statement applies only to static objects that are local to functions. If a function containing a local static object is never called, the constructor for that object is never executed, so the destructor is also not executed. The Obj A is a global object, so the constructor is always called for it before main( ) is entered, but the constructors for the static Obj B inside f( ) and the static Obj C inside g( ) are called only if those functions are called. To demonstrate which constructors and destructors are called, inside main( ) only f( ) is called. When main( ) exits, the destructors for the objects that have been constructed are called in reverse order of their construction. This means that if g( ) is called, the order in which the destructors for B and C are called depends on whether f( ) or g( ) is called first. Notice that the trace file ofstream object out is also a static object. It is important that its definition (as opposed to an extern declaration) appear at the beginning of the file, before there is any possible use of out. Otherwise you'll be using an object before it is properly initialized. In C++ the constructor for a global static object is called before main( ) is entered, so you now have a simple and portable way to execute code before entering main( ) and to execute code with the destructor after exiting main( ). In C this was always a trial that required you to root around in the compiler vendor's assembly-language startup code. Controlling linkage Ordinarily, any name at file scope (that is, not nested inside a class or function) is visible throughout all translation units in a program. This is often called external linkage because at link time the name is visible to the linker everywhere, external to that translation unit. Global variables and ordinary functions have external linkage. There are times when you'd like to limit the visibility of a name. An object or function name at file scope that is explicitly declared static is local to its translation unit (in the terms of this book, the.CPP file where the declaration occurs); that name has internal linkage. This means you can use the same name in other translation units without a name clash. One advantage to internal linkage is that the name can be placed in a header file without worrying that there will be a clash at link time. Names that are commonly placed in header files, such as const definitions and inline functions, default to internal linkage. Confusion Here's an example of how the two meanings of static can cross over each other. All global objects implicitly have static storage class, so if you say (at file scope), int a = 0; then storage for a will be in the program's static data area, and the initialization for a will occur once, before main( ) is entered. In addition, the visibility of a is global, across all translation units. In terms of visibility, the opposite of static (visible only in this translation unit) is extern, which explicitly states that the visibility of the name is across all translation units. So the above definition is equivalent to saying extern int a = 0; But if you say instead, static int a = 0; all you've done is change the visibility, so a has internal linkage. The storage class is unchanged - the object resides in the static data area whether the visibility is static or extern. Once you get into local variables, static stops altering the visibility (and extern has no meaning) and instead alters the storage class. Other storage class specifiers You will see static and extern used commonly. There are two other storage class specifiers that occur less often. The auto specifier is almost never used because it tells the compiler that this is a local variable. The compiler can always determine this fact from the context in which the variable is defined, so auto is redundant. A register variable is a local (auto) variable, along with a hint to the compiler that this particular variable will be heavily used, so the compiler ought to keep it in a register if it can. Thus, it is an optimization aid. Various compilers respond differently to this hint; they have the option to ignore it. If you take the address of the variable, the register specifier will almost certainly be ignored. You should avoid using register because the compiler can usually do a better job at of optimization than you. Namespaces Although names can be nested inside classes, the names of global functions, global variables, and classes are still in a single global name space. The static keyword gives you some control over this by allowing you to give variables and functions internal linkage (make them file static). But in a large project, lack of control over the global name space can cause problems. To solve these problems for classes, vendors often create long complicated names that are unlikely to clash, but then you're stuck typing those names. You can subdivide the global name space into more manageable pieces using the namespace feature of C++.34 The namespace keyword, like class, struct, enum, and union, puts the names of its members in a distinct space. While the other keywords have additional purposes, the creation of a new name space is the only purpose for namespace. There are significant differences with class, struct, union and enum, however: 6. A namespace definition can only appear at the global scope, but namespaces can be nested within each other. 7. No terminating semicolon is necessary after the closing brace of a namespace definition. I'll alias it: namespace Bob = BobsSuperDuperLibrary; 10. You cannot create an instance of a namespace as you can with a class. It is guaranteed that an unnamed space is unique for each translation unit. If you put local names in an unnamed namespace, you don't need to give them internal linkage by making them static. Using a namespace You can refer to a name within a namespace in two ways: one name at a time, using the scope resolution operator, and more expediently with the using keyword. The using directive Because it can rapidly get tedious to type the full qualification for an identifier in a namespace, the using keyword allows you to import an entire namespace at once. When used in conjunction with the namespace keyword, this is called a using directive. One aspect of the using directive may seem slightly counterintuitive at first. The visibility of the names introduced with a using directive is the scope where the directive is made. But you can override the names from the using directive as if they've been declared globally to that scope! The using declaration You can introduce names one at a time into the current scope with a using declaration. Unlike the using directive, which treats names as if they were declared globally to the scope, a using declaration is a declaration within the current scope. This means that if the namespace contains a set of overloaded functions with the same name, the using declaration declares all the functions in the overloaded set. You can put a using declaration anywhere a normal declaration can occur. A using declaration works like a normal declaration in all ways but one: it's possible for a using declaration to cause the overload of a function with the same argument types (which isn't allowed with normal overloading). This ambiguity, however, doesn't show up until the point of use, rather than the point of declaration. If you end up redeclaring the same function by importing different namespaces, it's OK - there won't be any ambiguities or duplications. Static members in C++ There are times when you need a single storage space to be used by all objects of a class. In C, you would use a global variable, but this is not very safe. Global data can be modified by anyone, and its name can clash with other identical names in a large project. It would be ideal if the data could be stored as if it were global, but be hidden inside a class, and clearly associated with that class. This is accomplished with static data members inside a class. There is a single piece of storage for a static data member, regardless of how many objects of that class you create. All objects share the same static storage space for that data member, so it is a way for them to communicate with each other. But the static data belongs to the class; its name is scoped inside the class and it can be public, private, or protected. Defining storage for static data members Because static data has a single piece of storage regardless of how many objects are created, that storage must be defined in a single place. The compiler will not allocate storage for you, although this was once true, with some compilers. The linker will report an error if a static data member is declared but not defined. The definition must occur outside the class (no inlining is allowed), and only one definition is allowed. Thus it is usual to put it in the implementation file for the class. The syntax sometimes gives people trouble, but it is actually quite logical. Some people have trouble with the idea that A::i is private, and yet here's something that seems to be manipulating it right out in the open. Doesn't this break the protection mechanism? It's a completely safe practice for two reasons. First, the only place this initialization is legal is in the definition. Indeed, if the static data were an object with a constructor, you would call the constructor instead of using the = operator. Secondly, once the definition has been made, the end-user cannot make a second definition - the linker will report an error. And the class creator is forced to create the definition, or the code won't link during testing. This ensures that the definition happens only once and that it's in the hands of the class creator. The entire initialization expression for a static member is in the scope of the class. These definitions have internal linkage, so they can be placed in header files. The syntax for initializing static arrays is the same as any aggregate, but you cannot use automatic counting. With the exception of the above paragraph, the compiler must have enough knowledge about the class to create an object by the end of the class declaration, including the exact sizes of all the components. Compile-time constants inside classes In Chapter 6 enumerations were introduced as a way to create a compile-time constant (one that can be evaluated by the compiler in a constant expression, such as an array size) that's local to a class. This practice, although commonly used, is often referred to as the enum hack because it uses enumerations in a way they were not originally intended. As with an ordinary global const used with a built-in type, no storage is allocated for the const, and it has internal linkage so no clashes occur. An additional advantage to this approach is that any built-in type may be made a member static const. With enum, you're limited to integral values. Nested and local classes You can easily put static data members in that are nested inside other classes. The definition of such members is an intuitive and obvious extension - you simply use another level of scope resolution. However, you cannot have static data members inside local classes (classes defined inside functions). In practice, local classes are used very rarely. Instead of making a global function that lives in and pollutes the global or local namespace, you bring the function inside the class. When you create a static member function, you are expressing an association with a particular class. A static member function cannot access ordinary data members, only static data members. It can call only other static member functions. Normally, the address of the current object (this) is quietly passed in when any member function is called, but a static member has no this, which is the reason it cannot access ordinary members. Thus, you get the tiny increase in speed afforded by a global function, which doesn't have the extra overhead of passing this, but the benefits of having the function inside the class. Using static to indicate that only one piece of storage for a class member exists for all objects of a class parallels its use with functions, to mean that only one copy of a local variable is used for all calls of a function. Here's an interesting feature: Because of the way initialization happens for static member objects, you can put a static data member of the same class inside that class. Here's an example that allows only a single object of type egg to exist by making the constructor private. Static initialization dependency Within a specific translation unit, the order of initialization of static objects is guaranteed to be the order in which the object definitions appear in that translation unit. The order of destruction is guaranteed to be the reverse of the order of initialization. However, there is no guarantee concerning the order of initialization of static objects across translation units, and there's no way to specify this order. This can cause significant problems. If the programming environment builds the program so that the first file is initialized before the second file, then there will be no problem. However, if the second file is initialized before the first, the constructor for oof relies upon the existence of out, which hasn't been constructed yet and this causes chaos. This is only a problem with static object initializers that depend on each other, because by the time you get into main( ), all constructors for static objects have already been called. In the previous example, zeroing of the storage occupied by the fstream out object has no special meaning, so it is truly undefined until the constructor is called. However, if the files are initialized in the opposite order, x is statically initialized to zero, y is dynamically initialized to one, and x then becomes two. Programmers must be aware of this because they can create a program with static initialization dependencies and get it working on one platform, but move it to another compiling environment where it suddenly, mysteriously, doesn't work. What to do There are three approaches to dealing with this problem: 1. Don't do it. Avoiding static initializer dependencies is the best solution. 2. If you must do it, put the critical static object definitions in a single file, so you can portably control their initialization by putting them in the correct order. This technique requires an additional class in your library header file. This class is responsible for the dynamic initialization of your library's static objects. The first time a translation unit containing DEPEND.H is initialized, init_count will be zero so the initialization will be performed. Cleanup happens in the reverse order, and ~Initializer( ) ensures that it will happen only once. This example used built-in types as the global static objects. The technique also works with classes, but those objects must then be dynamically initialized by the Initializer class. One way to do this is to create the classes without constructors and destructors, but instead with initialization and cleanup member functions using different names. A more common approach, however, is to have pointers to objects and to create them dynamically on the heap inside Initializer( ). This requires the use of two C++ keywords, new and delete, which will be explored in Chapter 11. Alternate linkage specifications What happens if you're writing a program in C++ and you want to use a C library? If you make the C function declaration, float f(int a, char b); the C++ compiler will mangle (decorate) this name to something like _f_int_int to support function overloading (and type-safe linkage). However, the C compiler that compiled your C library has most definitely not mangled the name, so its internal name will be _f. Thus, the linker will not be able to resolve your C++ calls to f( ). The escape mechanism provided in C++ is the alternate linkage specification, which was produced in the language by overloading the extern keyword. The only two types of linkage specifications supported by the standard are C and C++, but compiler vendors have the option of supporting other languages in the same way. The only alternate linkage specification strings that are standard are C and C++ but implementations can support other languages using the same mechanism. Summary The static keyword can be confusing because in some situations it controls the location of storage, and in others it controls visibility and linkage of a name. With the introduction of C++ namespaces, you have an improved and more flexible alternative to control the proliferation of names in large projects. The use of static inside classes is one more way to control names in a program. The names do not clash with global names, and the visibility and access is kept within the program, giving you greater control in the maintenance of your code. Exercises 1. Create a class that holds an array of ints. Set the size of the array using an untagged enumeration inside the class. Add a const int variable, and initialize it in the constructor initializer list. Add a static int member variable and initialize it to a specific value. Add a static member function that prints the static data member. Add an inline constructor and an inline member function called print( ) to print out all the values in the array, and to call the static member function. 2. In STATDEST.CPP, experiment with the order of constructor and destructor calls by calling f( ) and g( ) inside main( ) in different orders. Does your compiler get it right? Make sure there's nothing else important running on your machine when you run the program or that your machine will handle faults robustly. 4. Create a class with a destructor that prints a message and then calls exit( ). Create a global static object of this class and see what happens. 5. Modify VOLATILE.CPP from Chapter 6 to make comm::isr( ) something that would actually work as an interrupt service routine. Although references also exist in Pascal, the C++ version was taken from the Algol language. They are essential in C++ to support the syntax of operator overloading (see Chapter 10), but are also a general convenience to control the way arguments are passed into and out of functions. This chapter will first look briefly at the differences between pointers in C and C++, then introduce references. But the bulk of the chapter will delve into a rather confusing issue for the new C++ programmer: the copy-constructor, a special constructor (requiring references) that makes a new object from an existing object of the same type. The copy-constructor is used by the compiler to pass and return objects by value into and out of functions. Finally, the somewhat obscure C++ pointer-to-member feature is illuminated. Pointers in C++ The most important difference between pointers in C and in C++ is that C++ is a more strongly typed language. This stands out where void* is concerned. The compiler gives you an error message, and if you really want to do it, you must make it explicit, both to the compiler and to the reader, using a cast. It is usually used for function argument lists and function return values. But you can also make a free-standing reference. For example, int x; int and r = x; When a reference is created, it must be initialized to a live object. However, you can also say int and q = 12; Here, the compiler allocates a piece of storage, initializes it with the value 12, and ties the reference to that piece of storage. The point is that any reference must be tied to someone else's piece of storage. When you access a reference, you're accessing that storage. Thus if you say, int x = 0; int and a = x; a++; incrementing a is actually incrementing x. Again, the easiest way to think about a reference is as a fancy pointer. One advantage of this pointer is you never have to wonder whether it's been initialized (the compiler enforces it) and how to dereference it (the compiler does it). There are certain rules when using references: 6. A reference must be initialized when it is created. You must always be able to assume that a reference is connected to a legitimate piece of storage. References in functions The most common place you'll see references is in function arguments and return values. When a reference is used as a function argument, any modification to the reference inside the function will cause changes to the argument outside the function. Of course, you could do the same thing by passing a pointer, but a reference has much cleaner syntax. Whatever the reference is connected to shouldn't go away when the function returns; otherwise you'll be referring to unknown memory. In the call to g( ), an address is being passed (via a reference), but you don't see it. If it is a const object, the function g( ) will not accept the argument, which is actually a good thing, because the function does modify the outside argument. If you know the function will respect the constness of an object, making the argument a const reference will allow the function to be used in all situations. This means that, for built-in types, the function will not modify the argument, and for user-defined types the function will call only const member functions, and won't modify any public data members. The use of const references in function arguments is especially important because your function may receive a temporary object, created as a return value of another function or explicitly by the user of your function. Temporary objects are always const, so if you don't use a const reference, that argument won't be accepted by the compiler. It does so by allocating storage for an int, initializing it to one and producing the address to bind to the reference. The storage must be a const because changing it would make no sense - you can never get your hands on it again. With all temporary objects you must make the same assumption, that they're inaccessible. It's valuable for the compiler to tell you when you're changing such data because the result would be lost information. The function argument becomes a reference to a pointer, and you no longer have to take the address of that pointer. Argument-passing guidelines Your normal habit when passing an argument to a function should be to pass by const reference. This is the subject of the next section. The copy-constructor Now that you understand the basics of the reference in C++, you're ready to tackle one of the more confusing concepts in the language: the copy-constructor, often called X(Xand) (X of X ref). This constructor is essential to control passing and returning of user-defined types by value during function calls. Passing and returning by value To understand the need for the copy-constructor, consider the way C handles passing and returning variables by value during function calls. If you declare a function and make a function call, int f(int x, char c); int g = f(a, b); how does the compiler know how to pass and return those variables? It just knows! The range of the types it must deal with is so small - char, int, float, and double and their variations - that this information is built into the compiler. This is also true for the expression for g. The appearance of the call to f( ) will depend on your name-mangling scheme, and register a depends on how the CPU registers are named within your assembler. The logic behind the code, however, will remain the same. In C and C++, arguments are pushed on the stack from right to left, the function call is made, then the calling code is responsible for cleaning the arguments off the stack (which accounts for the add sp,4). But notice that to pass the arguments by value, the compiler simply pushes copies on the stack - it knows how big they are and that pushing those arguments makes accurate copies of them. The return value of f( ) is placed in a register. Again, the compiler knows everything there is to know about the return value type because it's built into the language, so the compiler can return it by placing it in a register. The simple act of copying the bits of the value is equivalent to copying the object. Passing and returning large objects But now consider user-defined types. If you create a class and you want to pass an object of that class by value, how is the compiler supposed to know what to do? This is no longer a built-in type the compiler writer knows about; it's a type someone has created since then. In main( ), the call to bigfun( ) starts as you might guess - the entire contents of B is pushed on the stack. In PASSTRUC.CPP, however, you'll see an additional action: The address of B2 is pushed before making the call, even though it's obviously not an argument. To comprehend what's going on here, you need to understand the constraints on the compiler when it's making a function call. Function-call stack frame When the compiler generates code for a function call, it first pushes all the arguments on the stack, then makes the call. Inside the function itself, code is generated to move the stack pointer down even further to provide storage for the function's local variables. This address is of course sacred, because without it your program will get completely lost. I shall call this block of memory, which is everything used by a function in the process of the function call, the function frame. You might think it reasonable to try to return values on the stack. The compiler could simply push it, and the function could return an offset to indicate how far down in the stack the return value begins. Re-entrancy The problem occurs because functions in C and C++ support interrupts; that is, the languages are re-entrant. They also support recursive function calls. This means that at any point in the execution of a program an interrupt can occur without disturbing the program. An ISR function call is triggered by some hardware event rather than an explicit call from within a program.) Now imagine what would happen if the called function tried to return values on the stack from an ordinary function. You can't touch any part of the stack that's above the return address, so the function would have to push the values below the return address. If you're trying to return values on the stack below the return address, you become vulnerable at that moment because an interrupt could come along. The ISR would move the stack pointer down to hold its return address and its local variables and overwrite your return value. To solve this problem, the caller could be responsible for allocating the extra storage on the stack for the return values before calling the function. However, C was not designed this way, and C++ must be compatible. As you'll see shortly, the C++ compiler uses a more efficient scheme. Your next idea might be to return the value in some global data area, but this doesn't work either. Re-entrancy means that any function can interrupt any other function, including the same function you're currently inside. Thus, if you put the return value in a global area, you might return into the same function, which would overwrite that return value. The same logic applies to recursion. The only safe place to return values is in the registers, so you're back to the problem of what to do when the registers aren't large enough to hold the return value. The answer is to push the address of the return value's destination on the stack as one of the function arguments, and let the function copy the return information directly into the destination. This not only solves all the problems, it's more efficient. It's also the reason that, in PASSTRUC.CPP, the compiler pushes the address of B2 before the call to bigfun( ) in main( ). If you look at the assembly output for bigfun( ), you can see it expects this hidden argument and performs the copy to the destination inside the function. Bitcopy versus initialization So far, so good. There's a workable process for passing and returning large simple structures. But notice that all you have is a way to copy the bits from one place to another, which certainly works fine for the primitive way that C looks at variables. But in C++ objects can be much more sophisticated than a patch of bits; they have meaning. This meaning may not respond well to having its bits copied. Consider a simple example: a class that knows how many objects of its type exist at any one time. The constructor increments the count each time an object is created, and the destructor decrements it. But after the call to f( ) you would expect to have an object count of two, because h2 is now in scope as well. Instead, the count is zero, which indicates something has gone horribly wrong. This is confirmed by the fact that the two destructors at the end make the object count go negative, something that should never happen. Look at the point inside f( ), which occurs after the argument is passed by value. This means the original object h exists outside the function frame, and there's an additional object inside the function frame, which is the copy that has been passed by value. However, the argument has been passed using C's primitive notion of bitcopying, whereas the C++ HowMany class requires true initialization to maintain its integrity, so the default bitcopy fails to produce the desired effect. When the local object goes out of scope at the end of the call to f( ), the destructor is called, which decrements object_count, so outside the function, object_count is zero. The creation of h2 is also performed using a bitcopy, so the constructor isn't called there, either, and when h and h2 go out of scope, their destructors cause the negative values of object_count. Copy-construction The problem occurs because the compiler makes an assumption about how to create a new object from an existing object. When you pass an object by value, you create a new object, the passed object inside the function frame, from an existing object, the original object outside the function frame. This is also often true when returning an object from a function. In the expression HowMany h2 = f(h); h2, a previously unconstructed object, is created from the return value of f( ), so again a new object is created from an existing one. The compiler's assumption is that you want to perform this creation using a bitcopy, and in many cases this may work fine but in HowMany it doesn't fly because the meaning of initialization goes beyond simply copying. Another common example occurs if the class contains pointers - what do they point to, and should you copy them or should they be connected to some new piece of memory? Fortunately, you can intervene in this process and prevent the compiler from doing a bitcopy. You do this by defining your own function to be used whenever the compiler needs to make a new object from an existing object. Logically enough, you're making a new object, so this function is a constructor, and also logically enough, the single argument to this constructor has to do with the object you're constructing from. Here, references come to the rescue, so you take the reference of the source object. This function is called the copy-constructor and is often referred to as X(Xand), which is its appearance for a class called X. If you create a copy-constructor, the compiler will not perform a bitcopy when creating a new object from an existing one. It will always call your copy-constructor. So, if you don't create a copy-constructor, the compiler will do something sensible, but you have the choice of taking over complete control of the process. First, the character buffer id acts as an object identifier so you can figure out which object the information is being printed about. Next is the copy-constructor, HowMany2(HowMany2and). The copy-constructor can create a new object only from an existing one, so the existing object's name is copied to id, followed by the word copy so you can see where it came from. Note the use of the Standard C library function strncat( ) to copy a maximum number of characters into id, again to prevent overrunning the end of the buffer. Inside the copy-constructor, the object count is incremented just as it is inside the normal constructor. This means you'll now get an accurate object count when passing and returning by value. The print( ) function has been modified to print out a message, the object identifier, and the object count. It must now access the id data of a particular object, so it can no longer be a static member function. Inside main( ), you can see a second call to f( ) has been added. However, this call uses the common C approach of ignoring the return value. The output of the program will throw some illumination on this. Then an ofstream is created for the same file, overwriting it. The line numbers are printed right-aligned in a field width of two, so the output still lines up in its original configuration. You can change the program to add an optional second command-line argument that allows the user to select a field width, or you can be more clever and count all the lines in the file to determine the field width automatically. But then, as f( ) is entered, the copy-constructor is quietly called by the compiler to perform the pass-by-value. A new object is created, which is the copy of h (thus the name h copy) inside the function frame of f( ), so the object count becomes two, courtesy of the copy-constructor. Line eight indicates the beginning of the return from f( ). But before the local variable h copy can be destroyed (it goes out of scope at the end of the function), it must be copied into the return value, which happens to be h2. A previously unconstructed object (h2) is created from an existing object (the local variable inside f( )), so of course the copy-constructor is used again in line nine. Now the name becomes h copy copy for h2's identifier because it's being copied from the copy that is the local object inside f( ). After the object is returned, but before the function ends, the object count becomes temporarily three, but then the local object h copy is destroyed. After the call to f( ) completes in line 13, there are only two objects, h and h2, and you can see that h2 did indeed end up as h copy copy. Temporary objects Line 15 begins the call to f(h), this time ignoring the return value. You can see in line 16 that the copy-constructor is called just as before to pass the argument in. And also, as before, line 21 shows the copy-constructor is called for the return value. But the copy-constructor must have an address to work on as its destination (a this pointer). Where is the object returned to? It turns out the compiler can create a temporary object whenever it needs one to properly evaluate an expression. In this case it creates one you don't even see to act as the destination for the ignored return value of f( ). The lifetime of this temporary object is as short as possible so the landscape doesn't get cluttered up with temporaries waiting to be destroyed, taking up valuable resources. Now, in lines 28-31, the h2 object is destroyed, followed by h, and the object count goes correctly back to zero. Default copy-constructor Because the copy-constructor implements pass and return by value, it's important that the compiler will create one for you in the case of simple structures - effectively, the same thing it does in C. However, all you've seen so far is the default primitive behavior: a bitcopy. When more complex types are involved, the C++ compiler will still automatically create a copy-constructor if you don't make one. Again, however, a bitcopy doesn't make sense, because it doesn't necessarily implement the proper meaning. Here's an example to show the more intelligent approach the compiler takes. Suppose you create a new class composed of objects of several existing classes. This is called, appropriately enough, composition, and it's one of the ways you can make new classes from existing classes. Now take the role of a naive user who's trying to solve a problem quickly by creating the new class this way. You don't know about copy-constructors, so you don't create one. In the class Composite, an object of WithCC is created using a default constructor. If there were no constructors at all in WithCC, the compiler would automatically create a default constructor, which would do nothing in this case. The class WoCC has no copy-constructor, but its constructor will store a message in an internal buffer that can be printed out using print( ). This constructor is explicitly called in Composite's constructor initializer list (briefly introduced in Chapter 6 and covered fully in Chapter 12). The reason for this becomes apparent later. The class Composite has member objects of both WithCC and WoCC (note the embedded object WOCC is initialized in the constructor-initializer list, as it must be), and no explicitly defined copy-constructor. However, in main( ) an object is created using the copy-constructor in the definition: Composite c2 = c; The copy-constructor for Composite is created automatically by the compiler, and the output of the program reveals how it is created. To create a copy-constructor for a class that uses composition (and inheritance, which is introduced in Chapter 12), the compiler recursively calls the copy-constructors for all the member objects and base classes. That is, if the member object also contains another object, its copy-constructor is also called. So in this case, the compiler calls the copy-constructor for WithCC. The output shows this constructor being called. Because WoCC has no copy-constructor, the compiler creates one for it, which is the default behavior of a bitcopy, and calls that inside the Composite copy-constructor. The call to Composite::print( ) in main shows that this happens because the contents of c2.WOCC are identical to the contents of c.WOCC. The process the compiler goes through to synthesize a copy-constructor is called memberwise initialization. It's best to always create your own copy-constructor rather than letting the compiler do it for you. This guarantees it will be under your control. Alternatives to copy-construction At this point your head may be swimming, and you might be wondering how you could have possibly written a functional class without knowing about the copy-constructor. But remember: You need a copy-constructor only if you're going to pass an object of your class by value. If that never happens, you don't need a copy-constructor. Preventing pass-by-value But, you say, if I don't make a copy-constructor, the compiler will create one for me. So how do I know that an object will never be passed by value? There's a simple technique for preventing pass-by-value: Declare a private copy-constructor. You don't even need to create a definition, unless one of your member functions or a friend function needs to perform a pass-by-value. If the user tries to pass or return the object by value, the compiler will produce an error message because the copy-constructor is private. It can no longer create a default copy-constructor because you've explicitly stated you're taking over that job. Functions that modify outside objects Reference syntax is nicer to use than pointer syntax, yet it clouds the meaning for the reader. For example, in the iostreams library one overloaded version of the get( ) function takes a charand as an argument, and the whole point of the function is to modify its argument by inserting the result of the get( ). Because of this, it's probably safer from a code maintenance standpoint to use pointers when you're passing the address of an argument to modify. If you always pass addresses as const references except when you intend to modify the outside object via the address, where you pass by non-const pointer, then your code is far easier for the reader to follow. Pointers to members A pointer is a variable that holds the address of some location, which can be either data or a function, so you can change what a pointer selects at run-time. The C++ pointer-to-member follows this same concept, except that what it selects is a location inside a class. The dilemma here is that all a pointer needs is an address, but there is no address inside a class; selecting a member of a class means offsetting into that class. You can't produce an actual address until you combine that offset with the starting address of a particular object. The syntax of pointers to members requires that you select an object at the same time you're dereferencing the pointer to member. To access what it's pointing at, you must dereference it with *. But it's an offset into an object, so you must also refer to that particular object. Thus, the * is combined with the object dereferencing. Now, what is the syntax for defining pm? Like any pointer, you have to say what type it's pointing at, and you use a * in the definition. The only difference is you must say what class of objects this pointer-to-member is used with. Of course, this is accomplished with the name of the class and the scope resolution operator. Thus, andsimple::a can be used only as pointer-to-member syntax. Functions A similar exercise produces the pointer-to-member syntax for member functions. A pointer to a function is defined like this: int (*fp)(float); The parentheses around (*fp) are necessary to force the compiler to evaluate the definition properly. Without them this would appear to be a function that returns an int*. To define and use a pointer to a member function, parentheses play a similarly important role. A pointer-to-member is no different; it allows you to choose a member at run-time. Typically, your classes will have only member functions publicly visible (data members are usually considered part of the underlying implementation), so the following example selects member functions at run-time. If the user must directly manipulate a pointer-to-member, then a typedef is in order. To really clean things up, you can use the pointer-to-member as part of the internal implementation mechanism. Here's the preceding example using a pointer-to-member inside the class. The code must even ask for the Count( ) of functions. This way, the class implementor can change the quantity of functions in the underlying implementation without affecting the code where the class is used. The initialization of the pointers-to-members in the constructor may seem overspecified. The problem is this doesn't conform to the pointer-to-member syntax, which is required so everyone, especially the compiler, can figure out what's going on. Again, the syntax requires that a pointer-to-member always be bound to an object when it is dereferenced. Summary Pointers in C++ are remarkably similar to pointers in C, which is good. Otherwise a lot of C code wouldn't compile properly under C++. The only compiler errors you will produce is where dangerous assignments occur. If these are in fact what are intended, the compiler errors can be removed with a simple (and explicit!) cast. C++ also adds the reference from Algol and Pascal, which is like a constant pointer that is automatically dereferenced by the compiler. A reference holds an address, but you treat it like an object. References are essential for clean syntax with operator overloading (the subject of the next chapter), but they also add syntactic convenience for passing and returning objects for ordinary functions. The copy-constructor takes a reference to an existing object of the same type as its argument, and it is used to create a new object from an existing one. The compiler automatically calls the copy-constructor when you pass or return an object by value. Although the compiler will automatically create a copy-constructor for you, if you think one will be needed for your class you should always define it yourself to ensure that the proper behavior occurs. If you don't want the object passed or returned by value, you should create a private copy-constructor. Pointers-to-members have the same functionality as ordinary pointers: You can choose a particular region of storage (data or function) at run-time. Pointers-to-members just happen to work with class members rather than global data or functions. You get the programming flexibility that allows you to change behavior at run-time. Exercises 1. Create a function that takes a charand argument and modifies that argument. In main( ), print out a char variable, call your function for that variable, and print it out again to prove to yourself it has been changed. How does this affect program readability? 2. Write a class with a copy-constructor that announces itself to cout. Now create a function that passes an object of your new class in by value and another one that creates a local object of your new class and returns it by value. Call these functions to prove to yourself that the copy-constructor is indeed quietly called when passing and returning objects by value. 3. Discover how to get your compiler to generate assembly language, and produce assembly for PASSTRUC.CPP. Trace through and demystify the way your compiler generates code to pass and return large structures. 4. (Advanced) This exercise creates an alternative to using the copy-constructor. Create a class X and declare (but don't define) a private copy-constructor. Make a public clone( ) function as a const member function that returns a copy of the object created using new (a forward reference to Chapter 11). Now create a function that takes as an argument a const Xand and clones a local copy that can be modified. The drawback to this approach is that you are responsible for explicitly destroying the cloned object (using delete) when you're done with it. The difference is the arguments for this function don't appear inside parentheses, but instead surrounding or next to characters you've always thought of as immutable operators. But in C++, it's possible to define new operators that work with classes. This definition is just like an ordinary function definition except the name of the function begins with the keyword operator and ends with the operator itself. That's the only difference, and it becomes a function like any other function, which the compiler calls when it sees the appropriate pattern. Warning and reassurance It's very tempting to become overenthusiastic with operator overloading. It's a fun toy, at first. But remember it's only syntactic sugar, another way of calling a function. Looking at it this way, you have no reason to overload an operator except that it will make the code involving your class easier to write and especially read. Another common response to operator overloading is panic: Suddenly, C operators have no familiar meaning anymore. Everything's changed and all my C code will do different things! This isn't true. All the operators used in expressions that contain only built-in data types cannot be changed. Only an expression containing a user-defined type can have an overloaded operator. Syntax Defining an overloaded operator is like defining a function, but the name of that function is operator@, where @ represents the operator. The number of arguments in the function argument list depends on two factors: 1. Whether it's a unary (one argument) or binary (two argument) operator. 2. Whether the operator is defined as a global function (one argument for unary, two for binary) or a member function (zero arguments for unary, one for binary - the object becomes the left-hand argument). The single argument is what appears on the right-hand side of the operator for binary operators. Unary operators have no arguments when defined as member functions. The member function is called for the object on the left-hand side of the operator. For nonconditional operators (conditionals usually return a Boolean value) you'll almost always want to return an object or reference of the same type you're operating on if the two arguments are the same type. If they're not, the interpretation of what it should produce is up to you. This way complex expressions can be built up: K += I + J; The operator+ produces a new Integer (a temporary) that is used as the rv argument for the operator+=. This temporary is destroyed as soon as it is no longer needed. Overloadable operators Although you can overload almost all the operators available in C, the use is fairly restrictive. This makes sense - all these actions would produce operators that confuse meaning rather than clarify it. The next two subsections give examples of all the regular operators, overloaded in the form that you'll most likely use. Unary operators The following example shows the syntax to overload all the unary operators, both in the form of global functions and member functions. These will expand upon the Integer class shown previously and add a new byte class. The meaning of your particular operators will depend on the way you want to use them, but consider the client programmer before doing something unexpected. Guidelines for how to pass and return arguments are given later. The above forms (and the ones that follow in the next section) are typically what you'll use, so start with them as a pattern when overloading your own operators. Increment and decrement The overloaded ++ and - - operators present a dilemma because you want to be able to call different functions depending on whether they appear before (prefix) or after (postfix) the object they're acting upon. The solution is simple, but some people find it a bit confusing at first. When the compiler sees, for example, ++a (a preincrement), it generates a call to operator++(a); but when it sees a++, it generates a call to operator++(a, int). That is, the compiler differentiates between the two forms by making different function calls. In UNARY.CPP for the member function versions, if the compiler sees ++b, it generates a call to B::operator++( ); and if it sees b++ it calls B::operator++(int). The user never sees the result of her action except that a different function gets called for the prefix and postfix versions. Underneath, however, the two functions calls have different signatures, so they link to two different function bodies. The compiler passes a dummy constant value for the int argument (which is never given an identifier because the value is never used) to generate the different signature for the postfix version. Binary operators The following listing repeats the example of UNARY.CPP for binary operators. Both global versions and member function versions are shown. This is explained later. Notice that all the assignment operators have code to check for self-assignment, as a general guideline. In some cases this is not necessary; for example, with operator+= you may want to say A+=A and have it add A to itself. The most important place to check for self-assignment is operator= because with complicated objects disastrous results may occur. It's also possible to overload operators to handle mixed types, so you can add apples to oranges, for example. Before you start on an exhaustive overloading of operators, however, you should look at the section on automatic type conversion later in this chapter. Often, a type conversion in the right place can save you a lot of overloaded operators. Arguments and return values It may seem a little confusing at first when you look at UNARY.CPP and BINARY.CPP and see all the different ways that arguments are passed and returned. Although you can pass and return arguments any way you want to, the choices in these examples were not selected at random. They follow a very logical pattern, the same one you'll want to use in most of your choices. 3. As with any function argument, if you only need to read from the argument and not change it, default to passing it as a const reference. Ordinary arithmetic operations (like + and -, etc.) and Booleans will not change their arguments, so pass by const reference is predominantly what you'll use. When the function is a class member, this translates to making it a const member function. Only with the operator-assignments (like +=) and the operator=, which change the left-hand argument, is the left argument not a constant, but it's still passed in as an address because it will be changed. 4. The type of return value you should select depends on the expected meaning of the operator. For example, Integer::operator+ must produce an Integer object that is the sum of the operands. This object is returned by value as a const, so the result cannot be modified as an lvalue. 5. All the assignment operators modify the lvalue. To allow the result of the assignment to be used in chained expressions, like A=B=C, it's expected that you will return a reference to that same lvalue that was just modified. But should this reference be a const or nonconst? Although you read A=B=C from left to right, the compiler parses it from right to left, so you're not forced to return a nonconst to support assignment chaining. However, people do sometimes expect to be able to perform an operation on the thing that was just assigned to, such as (A=B).foo( ); to call foo( ) on A after assigning B to it. Thus the return value for all the assignment operators should be a nonconst reference to the lvalue. 6. For the logical operators, everyone expects to get at worst an int back, and at best a bool. 7. The increment and decrement operators present a dilemma because of the pre- and postfix versions. Both versions change the object and so cannot treat the object as a const. The prefix version returns the value of the object after it was changed, so you expect to get back the object that was changed. Thus, with prefix you can just return *this as a reference. The postfix version is supposed to return the value before the value is changed, so you're forced to create a separate object to represent that value and return it. Thus, with postfix you must return by value if you want to preserve the expected meaning. Now the question is: Should these be returned as const or nonconst? Because of the variety of meanings you may want to give the increment and decrement operators, they will need to be considered on a case-by-case basis. Return by value as const Returning by value as a const can seem a bit subtle at first, and so deserves a bit more explanation. Consider the binary operator+. If you use it in an expression such as f(A+B), the result of A+B becomes a temporary object that is used in the call to f( ). Because it's a temporary, it's automatically const, so whether you explicitly make the return value const or not has no effect. However, it's also possible for you to send a message to the return value of A+B, rather than just passing it to a function. For example, you can say (A+B).g( ), where g( ) is some member function of Integer, in this case. By making the return value const, you state that only a const member function can be called for that return value. This is const-correct, because it prevents you from storing potentially valuable information in an object that will most likely be lost. In operator+, for example: return Integer(left.i + right.i); This may look at first like a function call to a constructor, but it's not. The syntax is that of a temporary object; the statement says make a temporary Integer object and return it. Because of this, you might think that the result is the same as creating a named local object and returning that. However, it's quite different. If you were to say instead: Integer tmp(left.i + right.i); return tmp; three things will happen. First, the tmp object is created including its constructor call. Then, the copy-constructor copies the tmp to the location of the outside return value. Finally, the destructor is called for tmp at the end of the scope. In contrast, the returning a temporary approach works quite differently. When the compiler sees you do this, it knows that you have no other need for the object it's creating than to return it so it builds the object directly into the location of the outside return value. This requires only a single ordinary constructor call (no copy-constructor is necessary) and there's no destructor call because you never actually create a local object. Thus, while it doesn't cost anything but programmer awareness, it's significantly more efficient. Unusual operators Several additional operators have a slightly different syntax for overloading. Because it implies that the object acts like an array, you will often return a reference from this operator, so it can be used conveniently on the left-hand side of an equal sign. This operator is commonly overloaded; you'll see examples in the rest of the book. The comma operator is called when it appears next to an object of the type the comma is defined for. However, operator, is not called for function argument lists, only for objects that are out in the open, separated by commas. There doesn't seem to be a lot of practical uses for this operator; it's in the language for consistency. The usage shown is fairly obscure and questionable. Although you would probably use a comma-separated list as part of a more complex expression, it's too subtle to use in most situations. The function call operator( ) must be a member function, and it is unique in that it allows any number of arguments. It makes your object look like it's actually a function name, so it's probably best used for types that only have a single operation, or at least an especially prominent one. The operators new and delete control dynamic storage allocation, and can be overloaded. This very important topic is covered in the next chapter. It is provided for those situations when you want to mimic the behavior provided by the built-in pointer-to-member syntax, described in the previous chapter. It has additional, atypical constraints: It must return either an object (or reference to an object) that also has a smart pointer or a pointer that can be used to select what the smart pointer arrow is pointing at. The functions f( ) and g( ) simply print out interesting values using static data members. Pointers to these objects are stored inside containers of type ObjContainer using its add( ) function. ObjContainer looks like an array of pointers, but you'll notice there's no way to get the pointers back out again. However, Sp is declared as a friend class, so it has permission to look inside the container. Notice that an iterator is a custom fit for the container it's created for - unlike a pointer, there isn't a general purpose iterator. Containers and iterators are covered in more depth in Chapter XX. In main( ), once the container oc is filled with Obj objects, an iterator SP is created. Although the underlying mechanics of the smart pointer are more complex than the other operators, the goal is exactly the same - to provide a more convenient syntax for the users of your classes. Operators you can't overload There are certain operators in the available set that cannot be overloaded. The general reason for the restriction is safety: If these operators were overloadable, it would somehow jeopardize or break safety mechanisms. Often it makes things harder, or confuses existing practice. The most popular choice for this was operator** from Fortran, but this raised difficult parsing questions. Also, C has no exponentiation operator, so C++ didn't seem to need one either because you can always perform a function call. An exponentiation operator would add a convenient notation, but no new language functionality, to account for the added complexity of the compiler. There are no user-defined operators. That is, you can't make up new operators that aren't currently in the set. Part of the problem is how to determine precedence, and part of the problem is an insufficient need to account for the necessary trouble. You can't change the precedence rules. They're hard enough to remember as it is, without letting people play with them. Nonmember operators In some of the previous examples, the operators may be members or nonmembers, and it doesn't seem to make much difference. This usually raises the question, Which should I choose? In general, if it doesn't make any difference, they should be members, to emphasize the association between the operator and its class. When the left-hand operand is an object of the current class, it works fine. This isn't always the case - sometimes you want the left-hand operand to be an object of some other class. The overloaded shift operators pass and return by reference, so the actions will affect the external objects. Once all the actions are performed on the istream or ostream, it is returned so it can be used in a more complicated expression. The form shown in this example for the inserter and extractor is standard. If you want to create a set for your own class, copy the function signatures and return types and follow the form of the body. This is no doubt because the = sign is such a fundamental operation in programming, right down to copying a register at the machine level. In addition, the copy-constructor (from the previous chapter) can also be invoked when using the = sign: foo b; foo a = b; a = b; In the second line, the object a is being defined. A new object is being created where one didn't exist before. Because you know by now how defensive the C++ compiler is about object initialization, you know that a constructor must always be called at the point where an object is defined. But which constructor? So even though an equal sign is involved, the copy-constructor is called. In the third line, things are different. On the left side of the equal sign, there's a previously initialized object. Clearly, you don't call a constructor for an object that's already been created. In this case foo::operator= is called for a, taking as an argument whatever appears on the right-hand side. It's even better to avoid writing code that uses the = for initialization; instead, always use the explicit constructor form; the last line becomes Fee fum(fi); This way, you'll avoid confusing your readers. Behavior of operator= In BINARY.CPP, you saw that operator= can be only a member function. The compiler skirts this whole issue by forcing you to make operator= a member function. When you create an operator=, you must copy all the necessary information from the right-hand object into yourself to perform whatever you consider assignment for your class. A common mistake was made in this example. When you're assigning two objects of the same type, you should always check first for self-assignment: Is the object being assigned to itself? Pointers in classes What happens if the object is not so simple? For example, what if the object contains pointers to other objects? Simply copying a pointer means you'll end up with two objects pointing to the same storage location. In situations like these, you need to do bookkeeping of your own. There are two common approaches to this problem. The simplest technique is to copy whatever the pointer refers to when you do an assignment or a copy-constructor. The operator= checks for self-assignment as a matter of course, even though it's not strictly necessary here. This virtually eliminates the possibility that you'll forget to check for self-assignment if you do change the code so that it matters. Here, the constructors allocate the memory and initialize it, the operator= copies it, and the destructor frees the memory. However, if you're dealing with a lot of memory or a high overhead to initialize that memory, you may want to avoid this copying. A very common approach to this problem is called reference counting. You make the block of memory smart, so it knows how many objects are pointing to it. Then copy-construction or assignment means attaching another pointer to an existing block of memory and incrementing the reference count. Destruction means reducing the reference count and destroying the object if the reference count goes to zero. But what if you want to write to the block of memory? More than one object may be using this block, so you'd be modifying someone else's block as well as yours, which doesn't seem very neighborly. To solve this problem, an additional technique called copy-on-write is often used. Before writing to a block of memory, you make sure no one else is using it. If the reference count is greater than one, you must make yourself a personal copy of that block before writing it, so you don't disturb someone else's turf. There's a copy-constructor so you can make a new MemBlock from an existing one. The attach( ) function increments the reference count of a MemBlock to indicate there's another object using it. If the reference count goes to zero, then no one is using it anymore, so the member function destroys its own object by saying delete this. You can modify the memory with the set( ) function, but before you make any modifications, you should ensure that you aren't walking on a MemBlock that some other object is using. You do this by calling Counted::unalias( ), which in turn calls MemBlock::unalias( ). The latter function will return the block pointer if the reference count is one (meaning no one else is pointing to that block), but will duplicate the block if the reference count is more than one. This example includes a sneak preview of the next chapter. Instead of C's malloc( ) and free( ) to create and destroy the objects, the special C++ operators new and delete are used. For this example, you can think of new and delete just like malloc( ) and free( ), except new calls the constructor after allocating memory, and delete calls the destructor before freeing the memory. The copy-constructor, instead of creating its own memory, assigns block to the block of the source object. Then, because there's now an additional object using that block of memory, it increments the reference count by calling MemBlock::attach( ). The operator= deals with an object that has already been created on the left side of the =, so it must first clean that up by calling detach( ) for that MemBlock, which will destroy the old MemBlock if no one else is using it. Then operator= repeats the behavior of the copy-constructor. Notice that it first checks to detect whether you're assigning the same object to itself. The destructor calls detach( ) to conditionally destroy the MemBlock. To implement copy-on-write, you must control all the actions that write to your block of memory. This means you can't ever hand a raw pointer to the outside world. Instead you say, Tell me what you want done and I'll do it for you! For example, the write( ) member function allows you to change the values in the block of memory. But first, it uses unalias( ) to prevent the modification of an aliased block (a block with more than one Counted object using it). It also tests the copy-on-write by calling the write( ) function for object C, which is aliased to A's memory block. Tracing the output To verify that the behavior of this scheme is correct, the best approach is to add information and functionality to the class to generate a trace output that can be analyzed. The destructor announces which block is being destroyed, and the print( ) function displays the block number and reference count. The Counted class contains a buffer id to keep track of information about the object. The Counted constructor creates a new MemBlock object and assigns the result (a pointer to the MemBlock object on the heap) to block. The identifier, copied from the argument, has the word copy appended to show where it's copied from. Also, the addname( ) function lets you put additional information about the object in id (the actual identifier, so you can see what it is as well as where it's copied from). Automatic operator= creation Because assigning an object to another object of the same type is an activity most people expect to be possible, the compiler will automatically create a type::operator=(type) if you don't make one. The behavior of this operator mimics that of the automatically created copy-constructor: If the class contains objects (or is inherited from another class), the operator= for those objects is called recursively. This is called memberwise assignment. With classes of any sophistication (especially if they contain pointers!) you want to explicitly create an operator=. If you really don't want people to perform assignment, declare operator= as a private function. In C++, you can achieve this same effect for user-defined types by defining automatic type-conversion functions. These functions come in two flavors: a particular type of constructor and an overloaded operator. Constructor conversion If you define a constructor that takes as its single argument an object (or reference) of another type, that constructor allows the compiler to perform an automatic type conversion. Then it looks to see if there's any way to get a Two from a One, and it finds the constructor Two::Two(One), which it quietly calls. The resulting Two object is handed to f( ). In this case, automatic type conversion has saved you from the trouble of defining two overloaded versions of f( ). However, the cost is the hidden constructor call to Two, which may matter if you're concerned about the efficiency of calls to f( ). Preventing constructor conversion There are times when automatic type conversion via the constructor can cause problems. To turn it off, you modify the constructor by prefacing with the keyword explicit39 (which only works with constructors). If the user wants to make the conversion happen, the code must be written out. In the above code, f(Two(one)) creates a temporary object of type Two from one, just like the compiler did in the previous version. Operator conversion The second way to effect automatic type conversion is through operator overloading. You can create a member function that takes the current type and converts it to the desired type using the operator keyword followed by the type you want to convert to. This form of operator overloading is unique because you don't appear to specify a return type - the return type is the name of the operator you're overloading. The value of the constructor technique is you can add a new conversion path to an existing system as you're creating a new class. However, creating a single-argument constructor always defines an automatic type conversion (even if it's got more than one argument, if the rest of the arguments are defaulted), which may not be what you want. In addition, there's no way to use a constructor conversion from a user-defined type to a built-in type; this is possible only with operator overloading. If you want both operands to be converted, the global versions can save a lot of coding. In main( ), you can see that adding a Number to another Number works fine because it's an exact match to the overloaded operator. Also, when the compiler sees a Number followed by a + and an int, it can match to the member function Number::operator+ and convert the int argument to a Number using the constructor. But when it sees an int and a + and a Number, it doesn't know what to do because all it has is Number::operator+, which requires that the left operand already be a Number object. Thus the compiler issues an error. With the friend operator-, things are different. The compiler needs to fill in both its arguments however it can; it isn't restricted to having a Number as the left-hand argument. Thus, if it sees 1 - a, it can convert the first argument to a Number using the constructor. Sometimes you want to be able to restrict the use of your operators by making them members. For example, when multiplying a matrix by a vector, the vector must go on the right. But if you want your operators to be able to convert either argument, make the operator a friend function. Fortunately, the compiler will not take 1 - 1 and convert both arguments to Number objects and then call operator-. That would mean that existing C code might suddenly start to work differently. The compiler matches the simplest possibility first, which is the built-in operator for the expression 1 - 1. A perfect example: strings An example where automatic type conversion is extremely helpful occurs with a string class. Pitfalls in automatic type conversion Because the compiler must choose how to quietly perform a type conversion, it can get into trouble if you don't design your conversions correctly. A simple and obvious situation occurs with a class X that can convert itself to an object of class Y with an operator Y( ). If class Y has a constructor that takes a single argument of type X, this represents the identical type conversion. A more difficult problem to spot occurs when you provide automatic conversion to more than one type. The insidious thing about this is that there's no problem until someone innocently comes along and creates two overloaded versions of h( ). You can have conversions to other types; they just shouldn't be automatic. You can create explicit function calls with names like make_A( ) and make_B( ). Hidden activities Automatic type conversion can introduce more underlying activities than you may expect. However, Fo has an automatic type conversion to a Fee. There's no copy-constructor to create a Fee from a Fee, but this is one of the special functions the compiler can create for you. Automatic type conversion should be used carefully. It's excellent when it significantly reduces a coding task, but it's usually not worth using gratuitously. Summary The whole reason for the existence of operator overloading is for those situations when it makes life easier. There's nothing particularly magical about it; the overloaded operators are just functions with funny names, and the function calls happen to be made for you by the compiler when it spots the right pattern. But if operator overloading doesn't provide a significant benefit to you (the creator of the class) or the user of the class, don't confuse the issue by adding it. Exercises 1. Create a simple class with an overloaded operator++. Try calling this operator in both pre- and postfix form and see what kind of compiler warning you get. 2. Create a class that contains a single private char. You can test them with fstreams, strstreams, and stdiostreams (cin and cout). Choose the return values for these functions so that expressions can be chained together, and for efficiency. Write an automatic type conversion operator int( ). 4. Combine the classes in UNARY.CPP and BINARY.CPP. 5. Fix FANOUT.CPP by creating an explicit function to call to perform the type conversion, instead of one of the automatic conversion operators. 13: Dynamic object creation Sometimes you know the exact quantity, type, and lifetime of the objects in your program. But not always. How many planes will an air-traffic system have to handle? How many shapes will a CAD system need? How many nodes will there be in a network? To solve the general programming problem, it's essential that you be able to create and destroy objects at run-time. Of course, C has always provided the dynamic memory allocation functions malloc( )and free( )(along with variants of malloc( )) that allocate storage from the heap (also called the free store) at run-time. However, this simply won't work in C++. The constructor doesn't allow you to hand it the address of the memory to initialize, and for good reason: If you could do that, you might 6. Forget. Then guaranteed initialization of objects in C++ wouldn't be guaranteed. 7. Accidentally do something to the object before you initialize it, expecting the right thing to happen. 8. Hand it the wrong-sized object. And of course, even if you did everything correctly, anyone who modifies your program is prone to the same errors. Improper initialization is responsible for a large portion of programming errors, so it's especially important to guarantee constructor calls for objects created on the heap. So how does C++ guarantee proper initialization and cleanup, but allow you to create objects dynamically, on the heap? The answer is, by bringing dynamic object creation into the core of the language. malloc( ) and free( ) are library functions, and thus outside the control of the compiler. In this chapter, you'll learn how C++'s new and delete elegantly solve this problem by safely creating objects on the heap. Object creation When a C++ object is created, two events occur: 9. Storage is allocated for the object. The constructor is called to initialize that storage. By now you should believe that step two always happens. C++ enforces it because uninitialized objects are a major source of program bugs. It doesn't matter where or how the object is created - the constructor is always called. Step one, however, can occur in several ways, or at alternate times: 11. Storage can be allocated before the program begins, in the static storage area. This storage exists for the life of the program. Storage can be created on the stack whenever a particular execution point is reached (an opening brace). That storage is released automatically at the complementary execution point (the closing brace). These stack-allocation operations are built into the instruction set of the processor and are very efficient. However, you have to know exactly how much storage you need when you're writing the program so the compiler can generate the right code. Storage can be allocated from a pool of memory called the heap (also known as the free store). This is called dynamic memory allocation. To allocate this memory, a function is called at run-time; this means you can decide at any time that you want some memory and how much you need. You are also responsible for determining when to release the memory, which means the lifetime of that memory can be as long as you choose - it isn't determined by scope. Often these three regions are placed in a single contiguous piece of physical memory: the static area, the stack, and the heap (in an order determined by the compiler writer). However, there are no rules. The stack may be in a special place, and the heap may be implemented by making calls for chunks of memory from the operating system. As a programmer, these things are normally shielded from you, so all you need to think about is that the memory is there when you call for it. These functions are pragmatic but primitive and require understanding and care on the part of the programmer. C++ doesn't allow a void* to be assigned to any other pointer, so it must be cast. Because malloc( ) may fail to find any memory (in which case it returns zero), you must check the returned pointer to make sure it was successful. Notice that a constructor was not used because the constructor cannot be called explicitly - it's called for you by the compiler when an object is created. The problem here is that the user now has the option to forget to perform the initialization before the object is used, thus reintroducing a major source of bugs. Because C++ is attempting to make library use safe and effortless for the casual programmer, C's approach to dynamic memory is unacceptable. When you create an object with new (using a new-expression), it allocates enough storage on the heap to hold the object, and calls the constructor for that storage. By the time the pointer is assigned to fp, it's a live, initialized object - you can't even get your hands on it before then. It's also automatically the proper Foo type so no cast is necessary. The default new also checks to make sure the memory allocation was successful before passing the address to the constructor, so you don't have to explicitly determine if the call was successful. Later in the chapter you'll find out what happens if there's no memory left. You can create a new-expression using any constructor available for the class. It's as easy to create an object on the heap as it is on the stack. Just as a new-expression returns a pointer to the object, a delete-expression requires the address of an object. If you malloc( ) (or calloc( ) or realloc( )) an object and then delete it, the behavior is undefined. Because most default implementations of new and delete use malloc( ) and free( ), you'll probably release the memory without calling the destructor. If the pointer you're deleting is zero, nothing will happen. For this reason, people often recommend setting a pointer to zero immediately after you delete it, to prevent deleting it twice. Deleting an object more than once is definitely a bad thing to do, and will cause problems. Note, however, that even though the function is declared as a friend, it is defined as an inline! This is a mere convenience - defining a friend function as an inline to a class doesn't change the friend status or the fact that it's a global function and not a class member function. Also notice that the return value is the result of the entire output expression, which is itself an ostreamand (which it must be, to satisfy the return value type of the function). Memory manager overhead When you create auto objects on the stack, the size of the objects and their lifetime is built right into the generated code, because the compiler knows the exact quantity and scope. Creating objects on the heap involves additional overhead, both in time and in space. Here's a typical scenario. You call malloc( ), which requests a block of memory from the pool. The pool is searched for a block of memory large enough to satisfy the request. This is done by checking a map or directory of some sort that shows which blocks are currently in use and which blocks are available. It's a quick process, but it may take several tries so it might not be deterministic - that is, you can't necessarily count on malloc( ) always taking exactly the same amount of time. Before a pointer to that block is returned, the size and location of the block must be recorded so further calls to malloc( ) won't use it, and so that when you call free( ), the system knows how much memory to release. The way all this is implemented can vary widely. For example, there's nothing to prevent primitives for memory allocation being implemented in the processor. If you're curious, you can write test programs to try to guess the way your malloc( ) is implemented. You can also read the library source code, if you have it. Early examples redesigned Now that new and delete have been introduced (as well as many other subjects), the Stash and Stack examples from the early part of this book can be rewritten using all the features discussed in the book so far. Examining the new code will also give you a useful review of the topics. Heap-only string class At this point in the book, neither the Stash nor Stack classes will own the objects they point to; that is, when the Stash or Stack object goes out of scope, it will not call delete for all the objects it points to. The reason this is not possible is because, in an attempt to be generic, they hold void pointers. If you delete a void pointer, the only thing that happens is the memory gets released, because there's no type information and no way for the compiler to know what destructor to call. When a pointer is returned from the Stash or Stack object, you must cast it to the proper type before using it. These problems will be dealt with in the next chapter, and in Chapter 14. Because the container doesn't own the pointer, the user must be responsible for it. This means there's a serious problem if you add pointers to objects created on the stack and objects created on the heap to the same container because a delete-expression is unsafe for a pointer that hasn't been allocated on the heap. There are two approaches to this function. For ease of use, it can be a global friend function (called makeString( )), but if you don't want to pollute the global name space, you can make it a static member function (called make( )) and call it by saying String::make( ). The latter form has the benefit of more explicitly belonging to the class. This is how you create an array at run-time. The automatic type conversion to char* means that you can use a String object anywhere you need a char*. In addition, an iostream output operator extends the iostream library to handle String objects. Stash for pointers This version of the Stash class, which you last saw in Chapter 4, is changed to reflect all the new material introduced since Chapter 4. In addition, the new PStash holds pointers to objects that exist by themselves on the heap, whereas the old Stash in Chapter 4 and earlier copied the objects into the Stash container. With the introduction of new and delete, it's easy and safe to hold pointers to objects that have been created on the heap. Again, however, a void* is returned, so the user must remember what types are stored in the container and cast the pointers when fetching them out (a problem which will be repaired in future chapters). The inflate( ) code is actually more complicated and less efficient than in the earlier version. This is because realloc( ), which was used before, can resize an existing chunk of memory, or failing that, automatically copy the contents of your old chunk to a bigger piece. In either event you don't have to worry about it, and it's potentially faster if memory doesn't have to be moved. There's no equivalent of realloc( ) with new, however, so in this example you always have to allocate a bigger chunk, perform a copy, and delete the old chunk. In this situation it might make sense to use malloc( ), realloc( ), and free( ) in the underlying implementation rather than new and delete. In the first case, note the line: intStash.add(new int(i)); The expression new int(i) uses the pseudoconstructor form, so storage for a new int object is created on the heap, and the int is initialized to the value i. It's an undesirable effect of using void pointers as the underlying representation and will be fixed in later chapters. The second test opens the source code file and reads it into another PStash, converting each line into a String object. You can see that both makeString( ) and String::make( ) are used to show the difference between the two. The static member is probably the better approach because it's more explicit. This is not harmful here because the storage is released when the program ends, but it's not something you want to do in practice. It will be fixed in later chapters. The stack The Stack benefits greatly from all the features introduced since Chapter 3. The rest of the logic is virtually identical to what it was in Chapter 3. This program doesn't delete the pointers in the Stack and the Stack itself doesn't do it, so that memory is lost. There's one constraint, however: There must be a default constructor, except for aggregate initialization on the stack (see Chapter 3), because a constructor with no arguments must be called for every object. When creating arrays of objects on the heap using new, there's something else you must do. Now, however, you simply have a Foo*, which is exactly the same as you'd get if you said Foo* fp2 = new Foo; to create a single object. For fp2 this is fine, but for fp this means the other 99 destructor calls won't be made. The proper amount of storage will still be released, however, because it is allocated in one big chunk, and the size of the whole chunk is stashed somewhere by the allocation routine. The solution requires you to give the compiler the information that this is actually the starting address of an array. The additional overhead of letting the compiler handle it was very low, and it was considered better to specify the number of objects in one place rather than two. Making a pointer more like an array As an aside, the fp defined above can be changed to point to anything, which doesn't make sense for the starting address of an array. It makes more sense to define it as a constant, so any attempt to modify the pointer will be flagged as an error. Running out of storage What happens when the operator new cannot find a contiguous block of storage large enough to hold the desired object? A special function called the new-handler is called. Or rather, a pointer to a function is checked, and if the pointer is nonzero, then the function it points to is called. The default behavior for the new-handler is to throw an exception, the subject covered in Chapter 16. However, if you're using heap allocation in your program, it's wise to at least replace the new-handler with a message that says you've run out of memory and then aborts the program. That way, during debugging, you'll have a clue about what happened. For the final program you'll want to use more robust recovery. The while loop will keep allocating int objects (and throwing away their return addresses) until the free store is exhausted. At the very next call to new, no storage can be allocated, so the new-handler will be called. Of course, you can write more sophisticated new-handlers, even one to try to reclaim memory (commonly known as a garbage collector). This is not a job for the novice programmer. Overloading new and delete When you create a new-expression, two things occur: First, storage is allocated using the operator new, then the constructor is called. In a delete-expression, the destructor is called, then storage is deallocated using the operator delete. The constructor and destructor calls are never under your control (otherwise you might accidentally subvert them), but you can change the storage allocation functions operator new and operator delete. The memory allocation system used by new and delete is designed for general-purpose use. In special situations, however, it doesn't serve your needs. The most common reason to change the allocator is efficiency: You might be creating and destroying so many objects of a particular class that it has become a speed bottleneck. C++ allows you to overload new and delete to implement your own storage allocation scheme, so you can handle problems like this. Another issue is heap fragmentation: By allocating objects of different sizes it's possible to break up the heap so that you effectively run out of storage. That is, the storage might be available, but because of fragmentation no piece is big enough to satisfy your needs. By creating your own allocator for a particular class, you can ensure this never happens. In embedded and real-time systems, a program may have to run for a very long time with restricted resources. Such a system may also require that memory allocation always take the same amount of time, and there's no allowance for heap exhaustion or fragmentation. A custom memory allocator is the solution; otherwise programmers will avoid using new and delete altogether in such cases and miss out on a valuable C++ asset. When you overload operator new and operator delete, it's important to remember that you're changing only the way raw storage is allocated. The compiler will simply call your new instead of the default version to allocate storage, then call the constructor for that storage. So, although the compiler allocates storage and calls the constructor when it sees new, all you can change when you overload new is the storage allocation portion. Overloading new and delete is like overloading any other operator. However, you have a choice of overloading the global allocator or using a different allocator for a particular class. Overloading global new and delete This is the drastic approach, when the global versions of new and delete are unsatisfactory for the whole system. If you overload the global versions, you make the defaults completely inaccessible - you can't even call them from inside your redefinitions. The overloaded new must take an argument of size_t (the Standard C standard type for sizes). This argument is generated and passed to you by the compiler and is the size of the object you're responsible for allocating. You must return a pointer either to an object of that size (or bigger, if you have some reason to do so), or to zero if you can't find the memory (in which case the constructor is not called!). However, if you can't find the memory, you should probably do something more drastic than just returning zero, like calling the new-handler or throwing an exception, to signal that there's a problem. The return value of operator new is a void*, not a pointer to any particular type. All you've done is produce memory, not a finished object - that doesn't happen until the constructor is called, an act the compiler guarantees and which is out of your control. The operator delete takes a void* to memory that was allocated by operator new. It's a void* because you get that pointer after the destructor is called, which removes the object-ness from the piece of storage. The return type is void. These use the Standard C library functions malloc( ) and free( ) for the allocators (which is probably what the default new and delete use, as well!). However, they also print out messages about what they are doing. Notice that printf( ) and puts( ) are used rather than iostreams. Thus, when an iostream object is created (like the global cin, cout, and cerr), they call new to allocate memory. With printf( ), you don't get into a deadlock because it doesn't call new to initialize itself. In main( ), objects of built-in types are created to prove that the overloaded new and delete are also called in that case. Then a single object of type s is created, followed by an array. For the array, you'll see that extra memory is requested to put information about the number of objects in the array. In all cases, the global overloaded versions of new and delete are used. Overloading new and delete for a class Although you don't have to explicitly say static, when you overload new and delete for a class, you're creating static member functions. Again, the syntax is the same as overloading any other operator. When the compiler sees you use new to create an object of your class, it chooses the member operator new over the global version. However, the global versions of new and delete are used for all other types of objects (unless they have their own new and delete). In the following example, a very primitive storage allocation system is created for the class Framis. A chunk of memory is set aside in the static data area at program start-up, and that memory is used to allocate space for objects of type Framis. The allocation map is psize bytes long, so there's one byte for every block. All the bytes in the allocation map are initialized to zero using the aggregate initialization trick of setting the first element to zero so the compiler automatically initializes all the rest. The local operator new has the same form as the global one. All it does is search through the allocation map looking for a zero byte, then sets that byte to one to indicate it's been allocated and returns the address of that particular block. The operator delete assumes the Framis address was created in the pool. This is a fair assumption, because the local operator new will be called whenever you create a single Framis object on the heap - but not an array. Global new is used in that case. So the user might accidentally have called operator delete without using the empty bracket syntax to indicate array destruction. This would cause a problem. Also, the user might be deleting a pointer to an object created on the stack. If you think these things could occur, you might want to add a line to make sure the address is within the pool and on a correct boundary. In main( ), enough Framis objects are dynamically allocated to run out of memory; this checks the out-of-memory behavior. Then one of the objects is freed, and another one is created to show that the released memory is reused. Because this allocation scheme is specific to Framis objects, it's probably much faster than the general-purpose memory allocation scheme used for the default new and delete. Overloading new and delete for arrays If you overload operator new and delete for a class, those operators are called whenever you create an object of that class. However, if you create an array of those class objects, the global operator new is called to allocate enough storage for the array all at once, and the global operator delete is called to release that storage. Of course, you can use any memory allocation scheme you want in the overloaded new and delete. You can see that the array versions of new and delete are the same as the individual-object versions with the addition of the brackets. In both cases you're handed the size of the memory you must allocate. The size handed to the array version will be the size of the entire array. It's worth keeping in mind that the only thing the overloaded operator new is required to do is hand back a pointer to a large enough memory block. Although you may perform initialization on that memory, normally that's the job of the constructor that will automatically be called for your memory by the compiler. The constructor and destructor simply print out characters so you can see when they've been called. The operator new is called, then the constructor (indicated by the *). In a complementary fashion, calling delete causes the destructor to be called, then the operator delete. When an array of Widget objects is created, the array version of operator new is used, as promised. But notice that the size requested is four more bytes than expected. This extra four bytes is where the system keeps information about the array, in particular, the number of objects in the array. You can see that, even though the array operator new and operator delete are only called once for the entire array chunk, the default constructor and destructor are called for each object in the array. The constructor is not called in that case, so although you still have an unsuccessfully created object, at least you haven't invoked the constructor and handed it a zero pointer. Because new returns zero, the constructor is never called so its message is not printed. Object placement There are two other, less common, uses for overloading operator new. 1. You may want to place an object in a specific location in memory. This is especially important with hardware-oriented embedded systems where an object may be synonymous with a particular piece of hardware. 2. You may want to be able to choose from different allocators when calling new. Both of these situations are solved with the same mechanism: The overloaded operator new can take more than one argument. As you've seen before, the first argument is always the size of the object, which is secretly calculated and passed by the compiler. But the other arguments can be anything you want: the address you want the object placed at, a reference to a memory allocation function or object, or anything else that is convenient for you. For example, X* xp = new(a) X; will pass a as the second argument to operator new. Of course, this can work only if such an operator new has been declared. Thus, the caller decides where the object is going to sit, and the constructor is called for that memory as part of the new-expression. A dilemma occurs when you want to destroy the object. You will have serious problems if you call the destructor this way for an object created on the stack because the destructor will be called again at the end of the scope. If you call the destructor this way for an object that was created on the heap, the destructor will execute, but the memory won't be released, which probably isn't what you want. The only reason that the destructor can be called explicitly this way is to support the placement syntax for operator new. Although this example shows only one additional argument, there's nothing to prevent you from adding more if you need them for other purposes. In addition, you get a great deal of flexibility. You can change the behavior of new and delete if they don't suit your needs, particularly if they aren't efficient enough. Also, you can modify what happens when the heap runs out of storage. Create an object of that class with new, and destroy it with delete. Also create and destroy an array of these objects on the heap. 2. Create a PStash object, and fill it with new objects from Exercise 1. Observe what happens when this PStash object goes out of scope and its destructor is called. 3. Create a class with an overloaded operator new and delete, both the single-object versions and the array versions. Demonstrate that both versions work. 4. Devise a test for FRAMIS.CPP to show yourself approximately how much faster the custom new and delete run than the global new and delete. 14: Inheritance and composition One of the most compelling features about C++ is code reuse. But to be revolutionary, you've got to be able to do a lot more than copy code and change it. That's the C approach, and it hasn't worked very well. As with most everything in C++, the solution revolves around the class. You reuse code by creating new classes, but instead of creating them from scratch, you use existing classes that someone else has built and debugged. The trick is to use the classes without soiling the existing code. In this chapter you'll see two ways to accomplish this. The first is quite straightforward: You simply create objects of your existing class inside the new class. This is called composition because the new class is composed of objects of existing classes. The second approach is more subtle. You create a new class as a type of an existing class. You literally take the form of the existing class and add code to it, without modifying the existing class. This magical act is called inheritance, and most of the work is done by the compiler. Inheritance is one of the cornerstones of object-oriented programming and has additional implications that will be explored in the next chapter. It turns out that much of the syntax and behavior are similar for both composition and inheritance (which makes sense; they are both ways of making new types from existing types). In this chapter, you'll learn about these code reuse mechanisms. Composition syntax Actually, you've been using composition all along to create classes. You've just been composing classes using built-in types. It turns out to be almost as easy to use composition with user-defined types. It's probably more common to make the embedded objects private, so they become part of the underlying implementation (which means you can change the implementation if you want). When you do this, you automatically get all the data members and member functions in the base class. In fact, Y contains a subobject of X just as if you had created a member object of X inside Y instead of inheriting from X. Both member objects and base class storage are referred to as subobjects. In main( ) you can see that the data elements are added because the sizeof(Y) is twice as big as sizeof(X). You'll notice that the base class is preceded by public. During inheritance, everything defaults to private, which means all the public members of the base class are private in the derived class. This is almost never what you want; the desired result is to keep all the public members of the base class public in the derived class. You do this by using the public keyword during inheritance. In change( ), the base-class permute( ) function is called. The derived class has direct access to all the public base-class functions. The set( ) function in the derived class redefines the set( ) function in the base class. This means that if you don't like the version of a function you get during inheritance, you can change what it does. If, inside set( ), you simply call set( ) you'll get the local version of the function - a recursive function call. To call the base-class version, you must explicitly name it, using the base-class name and the scope resolution operator. The constructor initializer list You've seen how important it is in C++ to guarantee proper initialization, and it's no different during composition and inheritance. When an object is created, the compiler guarantees that constructors for all its subobjects are called. In the examples so far, all the subobjects have default constructors, and that's what the compiler automatically calls. But what happens if your subobjects don't have default constructors, or if you want to change a default argument in a constructor? This is a problem because the new class constructor doesn't have permission to access the private data elements of the subobject, so it can't initialize them directly. The solution is simple: Call the constructor for the subobject. C++ provides a special syntax for this, the constructor initializer list. The form of the constructor initializer list echoes the act of inheritance. With inheritance, you put the base classes after a colon and before the opening brace of the class body. In the constructor initializer list, you put the calls to subobject constructors after the constructor argument list and a colon, but before the opening brace of the function body. Member object initialization It turns out that you use this very same syntax for member object initialization when using composition. For composition, you give the names of the objects rather than the class names. Note that while you can see the type of the base class in the constructor initializer list, you only see the member object identifier. Built-in types in the initializer list The constructor initializer list allows you to explicitly call the constructors for member objects. In fact, there's no other way to call those constructors. The idea is that the constructors are all called before you get into the body of the new class's constructor. That way, any calls you make to member functions of subobjects will always go to initialized objects. There's no way to get to the opening brace of the constructor without some constructor being called for all the member objects and base-class objects, even if the compiler must make a hidden call to a default constructor. This is a further enforcement of the C++ guarantee that no object (or part of an object) can get out of the starting gate without its constructor being called. This idea that all the member objects are initialized by the opening brace of the constructor is a convenient programming aid, as well. Once you hit the opening brace, you can assume all subobjects are properly initialized and focus on specific tasks you want to accomplish in the constructor. However, there's a hitch: What about embedded objects of built-in types, which don't have constructors? To make the syntax consistent, you're allowed to treat built-in types as if they have a single constructor, which takes a single argument: a variable of the same type as the variable you're initializing. It's a convenient technique and a good coding style, so you'll often see it used. It's even possible to use the pseudoconstructor syntax when creating a variable of this type outside of a class: int i(100); This makes built-in types act a little bit more like objects. Remember, though, that these are not real constructors. In particular, if you don't explicitly make a pseudo-constructor call, no initialization is performed. Combining composition and inheritance Of course, you can use the two together. The following example shows the creation of a more complex class, using both inheritance and composition. You can see the constructor initializer list contains calls to both the base-class constructor and the member-object constructor. The function C::f( ) redefines B::f( ) that it inherits, and also calls the base-class version. In addition, it calls a.f( ). Notice that the only time you can talk about redefinition of functions is during inheritance; with a member object you can only manipulate the public interface of the object, not redefine it. In addition, calling f( ) for an object of class C would not call a.f( ) if C::f( ) had not been defined, whereas it would call B::f( ). However, the compiler still ensures that all destructors are called, and that means all the destructors in the entire hierarchy, starting with the most-derived destructor and working back to the root. It's worth emphasizing that constructors and destructors are quite unusual in that every one in the hierarchy is called, whereas with a normal member function only that function is called, but not any of the base-class versions. If you also want to call the base-class version of a normal member function that you're overriding, you must do it explicitly. Order of constructor and destructor calls It's interesting to know the order of constructor and destructor calls when an object has many subobjects. Then, to save some typing and demonstrate a macro technique that will be replaced by a much improved technique in Chapter 17, a macro is created to build some of the classes, which are then used in inheritance and composition. Each of the constructors and destructors report themselves to the trace file. Note that the constructors are not default constructors; they each have an int argument. The argument itself has no identifier; its only job is to force you to explicitly call the constructors in the initializer list. The destructors are called in exactly the reverse order of the constructors - this is important because of potential dependencies. It's also interesting that the order of constructor calls for member objects is completely unaffected by the order of the calls in the constructor initializer list. The order is determined by the order that the member objects are declared in the class. Name hiding If a base class has a function name that's overloaded several times, redefining that function name in the derived class will hide all the base-class versions. In each case, the compiler attempts to convert the argument into a Milhouse object and complains because it can't find a conversion. As you'll see in the next chapter, it's far more common to redefine functions using exactly the same signature and return type as in the base class. Functions that don't automatically inherit Not all functions are automatically inherited from the base class into the derived class. Thus, constructors and destructors don't inherit. In addition, the operator= doesn't inherit because it performs a constructor-like activity. That is, just because you know how to initialize all the members of an object on the left-hand side of the = from an object on the right-hand side doesn't mean that initialization will still have meaning after inheritance. In lieu of inheritance, these functions are synthesized by the compiler if you don't create them yourself. The synthesized constructors use memberwise initialization and the synthesized operator= uses memberwise assignment. In addition, the operator Other( ) performs automatic type conversion from a Root object to an object of the nested class Other. The class Derived simply inherits from Root and creates no functions (to see how the compiler responds). The function f( ) takes an Other object to test the automatic type conversion function. In main( ), the default constructor and copy-constructor are created and the Root versions are called as part of the constructor-call hierarchy. Even though it looks like inheritance, new constructors are actually created. As you might expect, no constructors with arguments are automatically created because that's too much for the compiler to intuit. The operator= is also synthesized as a new function in Derived using memberwise assignment because that function was not explicitly written in the new class. Because of all these rules about rewriting functions that handle object creation, it may seem a little strange at first that the automatic type conversion operator is inherited. Choosing composition vs. Both use the constructor initializer list to construct these subobjects. You may now be wondering what the difference is between the two, and when to choose one over the other. Composition is generally used when you want the features of an existing class inside your new class, but not its interface. That is, you embed an object that you're planning on using to implement features of your new class, but the user of your new class sees the interface you've defined rather than the interface from the original class. For this effect, you embed private objects of existing classes inside your new class. Sometimes it makes sense to allow the class user to directly access the composition of your new class, that is, to make the member objects public. The member objects use implementation hiding themselves, so this is a safe thing to do and when the user knows you're assembling a bunch of parts, it makes the interface easier to understand. With a little thought, you'll also see that it would make no sense to compose a car using a vehicle object - a car doesn't contain a vehicle, it is a vehicle. The is-a relationship is expressed with inheritance, and the has-a relationship is expressed with composition. Subtyping Now suppose you want to create a type of ifstream object that not only opens a file but also keeps track of the name of the file. So this approach won't work. In that case you're only using part of the class, and composition is appropriate. But what if you want everything in the class to come through? This is where inheritance is essential. That's because an FName2 is a type of ofstream; it doesn't simply contain one. This is a very important issue that will be explored at the end of this chapter and in Chapter 13. Specialization When you inherit, you take an existing class and make a special version of it. Generally, this means you're taking a general-purpose class and specializing it for a particular need. For example, consider the Stack class from the previous chapter. One of the problems with that class is that you had to perform a cast every time you fetched a pointer from the container. This is not only tedious, it's unsafe - you could cast the pointer to anything you want. An approach that seems better at first glance is to specialize the general Stack class using inheritance. Before, Stack would accept void pointers, so the user had no type checking to make sure the proper pointers were inserted. In addition, peek( ) and pop( ) now return String pointers rather than void pointers, so no cast is necessary to use the pointer. Amazingly enough, this extra type-checking safety is free! The compiler is being given extra type information, that it uses at compile-time, but the functions are inline and no extra code is generated. Unfortunately, inheritance doesn't solve all the problems with this container class. The destructor still causes trouble. You'll remember from Chapter 11 that the Stack::~Stack( ) destructor moves through the list and calls delete for all the pointers. The problem is, delete is called for void pointers, which only releases the memory and doesn't call the destructors (because void* has no type information). 3. No more inheritance is performed, because you'd end up with the same dilemma again: multiple destructor calls versus an incorrect destructor call (to a String object rather than what the class derived from Stringlist might contain). This issue will be revisited in the next chapter, but will not be fully solved until templates are introduced in Chapter 14. A more important observation to make about this example is that it changes the interface of the Stack in the process of inheritance. If the interface is different, then a Stringlist really isn't a Stack, and you will never be able to correctly use a Stringlist as a Stack. This questions the use of inheritance here: if you're not creating a Stringlist that is-a type of Stack, then why are you inheriting? A more appropriate version of Stringlist will be shown later in the chapter. The class user has no access to the underlying functionality, and an object cannot be treated as a member of the base class (as it was in FNAME2.CPP on page Error! Bookmark not defined.). You may wonder what the purpose of private inheritance is, because the alternative of creating a private object in the new class seems more appropriate. However, there may occasionally be situations where you want to produce part of the same interface as the base class and disallow the treatment of the object as if it were a base-class object. Publicizing privately inherited members When you inherit privately, all the public members of the base class become private. You should think carefully before using private inheritance instead of member objects; private inheritance has particular complications when combined with run-time type identification (the subject of Chapter 17). In an ideal world, private members would always be hard-and-fast private, but in real projects there are times when you want to make something hidden from the world at large and yet allow access for members of derived classes. Normally, you'll make the inheritance public so the interface of the base class is also the interface of the derived class. However, you can also use the protected keyword during inheritance. Protected derivation means implemented-in-terms-of to other classes but is-a for derived classes and friends. It's something you don't use very often, but it's in the language for completeness. Multiple inheritance You can inherit from one class, so it would seem to make sense to inherit from more than one class at a time. Indeed you can, but whether it makes sense as part of a design is a subject of continuing debate. One thing is generally agreed upon: You shouldn't try this until you've been programming quite a while and understand the language thoroughly. By that time, you'll probably realize that no matter how much you think you absolutely must use multiple inheritance, you can almost always get away with single inheritance. Initially, multiple inheritance seems simple enough: You add more classes in the base-class list during inheritance, separated by commas. However, multiple inheritance introduces a number of possibilities for ambiguity, which is why Chapter 15 is devoted to the subject. Incremental development One of the advantages of inheritance is that it supports incremental development by allowing you to introduce new code without causing bugs in existing code and isolating new bugs to the new code. By inheriting from an existing, functional class and adding data members and member functions (and redefining existing member functions) you leave the existing code - that someone else may still be using - untouched and unbugged. If a bug happens, you know it's in your new code, which is much shorter and easier to read than if you had modified the body of existing code. It's rather amazing how cleanly the classes are separated. You don't even need the source code for the member functions to reuse the code, just the header file describing the class and the object file or library file with the compiled member functions. You can do as much analysis as you want, but you still won't know all the answers when you set out on a project. You'll have much more success - and more immediate feedback - if you start out to grow your project as an organic, evolutionary creature, rather than constructing it all at once like a glass-box skyscraper. Although inheritance for experimentation is a useful technique, at some point after things stabilize you need to take a new look at your class hierarchy with an eye to collapsing it into a sensible structure. Upcasting Earlier in the chapter, you saw how an object of a class derived from ofstream has all the characteristics and behaviors of an ofstream object. In FName2.cpp, any ofstream member function could be called for an FName2 object. The most important aspect of inheritance is not that it provides member functions for the new class, however. It's the relationship expressed between the new class and the base class. This relationship can be summarized by saying, The new class is a type of the existing class. This description is not just a fanciful way of explaining inheritance - it's supported directly by the compiler. As an example, consider a base class called Instrument that represents musical instruments and a derived class called Wind. Because inheritance means that all the functions in the base class are also available in the derived class, any message you can send to the base class can also be sent to the derived class. So if the Instrument class has a play( ) member function, so will Wind instruments. This means we can accurately say that a Wind object is also a type of Instrument. However, in main( ) the tune( ) function is called by giving it a Wind object. Inside tune( ), the code works for Instrument and anything derived from Instrument, and the act of converting a Wind object, reference, or pointer into an Instrument object, reference, or pointer is called upcasting. Why upcasting? The reason for the term is historical and is based on the way class inheritance diagrams have traditionally been drawn: with the root at the top of the page, growing downward. Upcasting is always safe because you're going from a more specific type to a more general type - the only thing that can occur to the class interface is that it lose member functions, not gain them. This is why the compiler allows upcasting without any explicit casts or other special notation. Downcasting You can also perform the reverse of upcasting, called downcasting, but this involves a dilemma that is the subject of Chapter 17. To repair the problem you must remember to properly call the base-class copy-constructor (as the compiler does) whenever you write your own copy-constructor. What does it mean to pass a Child object to a Parent constructor? Here's the trick: Child is inherited from Parent, so a Child reference is a Parent reference. So the base-class copy-constructor upcasts a reference to Child to a reference to Parent and uses it to perform the copy-construction. When you write your own copy constructors you'll generally want to do this. Composition vs. Earlier in this chapter, the Stack class was specialized using inheritance. Bookmark not defined.), except that a Stack object is embedded in Stringlist, and member functions are called for the embedded object. There's still no time or space overhead because the subobject takes up the same amount of space, and all the additional type checking happens at compile time. You can also use private inheritance to express implemented in terms of. The method you use to create the Stringlist class is not critical in this situation - they all solve the problem adequately. One place it becomes important, however, is when multiple inheritance might be warranted. In that case, if you can detect a class where composition can be used instead of inheritance, you may be able to eliminate the need for multiple inheritance. Pointer and reference upcasting In WIND.CPP (page Error! Bookmark not defined.), the upcasting occurs during the function call - a Wind object outside the function has its reference taken and becomes an Instrument reference inside the function. A crisis Of course, any upcast loses type information about an object. If you say Wind w; Instrument* ip = andw; the compiler can deal with ip only as an Instrument pointer and nothing else. That is, it cannot know that ip actually happens to point to a Wind object. Thus you won't get the correct behavior. This is a significant problem; it is solved in the next chapter by introducing the third cornerstone of object-oriented programming: polymorphism (implemented in C++ with virtual functions). Summary Both inheritance and composition allow you to create a new type from existing types, and both embed subobjects of the existing types inside the new type. Typically, however, you use composition to reuse existing types as part of the underlying implementation of the new type and inheritance when you want to reuse the interface as well as the implementation. If the derived class has the base-class interface, it can be upcast to the base, which is critical for polymorphism as you'll see in the next chapter. Although code reuse through composition and inheritance is very helpful for rapid project development, you'll generally want to redesign your class hierarchy before allowing other programmers to become dependent on it. Your goal is a hierarchy where each class has a specific use and is neither too big (encompassing so much functionality that it's unwieldy to reuse) nor annoyingly small (you can't use it by itself or without adding functionality). Your finished classes should themselves be easily reused. Exercises 1. Modify CAR.CPP so it also inherits from a class called vehicle, placing appropriate member functions in vehicle (that is, make up some member functions). Add a nondefault constructor to vehicle, which you must call, inside car's constructor. 2. Create two classes, A and B, with default constructors that announce themselves. Inherit a new class called C from A, and create a member object of B in C, but do not create a constructor for C. Create an object of class C and observe the results. 3. Use inheritance to specialize the PStash class in Chapter 11 (PSTASH.H and PSTASH.CPP) so it accepts and returns String pointers. Also modify PSTEST.CPP and test it. Change the class so PStash is a member object. 4. Use private and protected inheritance to create two new classes from a base class. Then attempt to upcast objects of the derived class to the base class. Explain what happens. 5. Take the example CCRIGHT.CPP in this chapter and modify it by adding your own copy-constructor without calling the base-class copy-constructor and see what happens. Fix the problem by making a proper explicit call to the base-class copy constructor in the constructor-initializer list of the Child copy-constructor. 15: Polymorphism and virtual functions Polymorphism (implemented in C++ with virtual functions) is the third essential feature of an object-oriented programming language, after data abstraction and inheritance. It provides another dimension of separation of interface from implementation, to decouple what from how. Polymorphism allows improved code organization and readability as well as the creation of extensible programs that can be grown not only during the original creation of the project, but also when new features are desired. Encapsulation creates new data types by combining characteristics and behaviors. Implementation hiding separates the interface from the implementation by making the details private. This sort of mechanical organization makes ready sense to someone with a procedural programming background. But virtual functions deal with decoupling in terms of types. In the last chapter, you saw how inheritance allows the treatment of an object as its own type or its base type. This ability is critical because it allows many types (derived from the same base type) to be treated as if they were one type, and a single piece of code to work on all those different types equally. The virtual function allows one type to express its distinction from another, similar type, as long as they're both derived from the same base type. This distinction is expressed through differences in behavior of the functions you can call through the base class. In this chapter, you'll learn about virtual functions starting from the very basics, with simple examples that strip away everything but the virtualness of the program. Evolution of C++ programmers C programmers seem to acquire C++ in three steps. First, as simply a better C, because C++ forces you to declare all functions before using them and is much pickier about how variables are used. You can often find the errors in a C program simply by compiling it with a C++ compiler. Most programmers who have been working with C for a while quickly see the usefulness of this because, whenever they create a library, this is exactly what they try to do. With C++, you have the aid of the compiler. You can get stuck at the object-based level because it's very easy to get to and you get a lot of benefit without much mental effort. It's also easy to feel like you're creating data types - you make classes, and objects, and you send messages to those objects, and everything is nice and neat. But don't be fooled. If you stop here, you're missing out on the greatest part of the language, which is the jump to true object-oriented programming. You can do this only with virtual functions. Virtual functions enhance the concept of type rather than just encapsulating code inside structures and behind walls, so they are without a doubt the most difficult concept for the new C++ programmer to fathom. However, they're also the turning point in the understanding of object-oriented programming. If you don't use virtual functions, you don't understand OOP yet. Because the virtual function is intimately bound with the concept of type, and type is at the core of object-oriented programming, there is no analog to the virtual function in a traditional procedural language. As a procedural programmer, you have no referent with which to think about virtual functions, as you do with almost every other feature in the language. Features in a procedural language can be understood on an algorithmic level, but virtual functions can be understood only from a design viewpoint. Upcasting In the last chapter you saw how an object can be used as its own type or as an object of its base type. In addition, it can be manipulated through an address of the base type. Taking the address of an object (either a pointer or a reference) and treating it as the address of the base type is called upcasting because of the way inheritance trees are drawn with the base class at the top. In main( ), you can see this happening as a Wind object is passed to tune( ), with no cast necessary. This is acceptable; the interface in Instrument must exist in Wind, because Wind is publicly inherited from Instrument. Upcasting from Wind to Instrument may narrow that interface, but it cannot make it any less than the full interface to Instrument. The same arguments are true when dealing with pointers; the only difference is that the user must explicitly take the addresses of objects as they are passed into the function. The problem The problem with WIND2.CPP can be seen by running the program. The output is Instrument::play. This is clearly not the desired output, because you happen to know that the object is actually a Wind and not just an Instrument. The call should resolve to Wind::play. For that matter, any object of a class derived from Instrument should have its version of play used, regardless of the situation. However, the behavior of WIND2.CPP is not surprising, given C's approach to functions. To understand the issues, you need to be aware of the concept of binding. Function call binding Connecting a function call to a function body is called binding. When binding is performed before the program is run (by the compiler and linker), it's called early binding. You may not have heard the term before because it's never been an option with procedural languages: C compilers have only one kind of function call, and that's early binding. The problem in the above program is caused by early binding because the compiler cannot know the correct function to call when it has only an Instrument address. The solution is called late binding, which means the binding occurs at run-time, based on the type of the object. Late binding is also called dynamic binding or run-time binding. When a language implements late binding, there must be some mechanism to determine the type of the object at run-time and call the appropriate member function. That is, the compiler still doesn't know the actual object type, but it inserts code that finds out and calls the correct function body. The late-binding mechanism varies from language to language, but you can imagine that some sort of type information must be installed in the objects themselves. You'll see how this works later. Late binding occurs only with virtual functions, and only when you're using an address of the base class where those virtual functions exist, although they may also be defined in an earlier base class. To create a member function as virtual, you simply precede the declaration of the function with the keyword virtual. You don't repeat it for the function definition, and you don't need to repeat it in any of the derived-class function redefinitions (though it does no harm to do so). If a function is declared as virtual in the base class, it is virtual in all the derived classes. The redefinition of a virtual function in a derived class is often called overriding. Extensibility With play( ) defined as virtual in the base class, you can add as many new types as you want to the system without changing the tune( ) function. In a well-designed OOP program, most or all of your functions will follow the model of tune( ) and communicate only with the base-class interface. Such a program is extensible because you can add new functionality by inheriting new data types from the common base class. The functions that manipulate the base-class interface will not need to be changed at all to accommodate the new classes. The adjust( ) function is not redefined for Brass and Woodwind. When this happens, the previous definition is automatically used - the compiler guarantees there's always some definition for a virtual function, so you'll never end up with a call that doesn't bind to a function body. This array and the function f( ) will be used in later discussions. In the call to tune( ), upcasting is performed on each different type of object, yet the desired behavior always takes place. This is not an analysis or design error; it simply means you didn't have all the information the first time. Because of the tight class modularization in C++, it isn't a large problem when this occurs because changes you make in one part of a system tend not to propagate to other parts of the system as they do in C. How C++ implements late binding How can late binding happen? All the work goes on behind the scenes by the compiler, which installs the necessary late-binding mechanism when you ask it to (you ask by creating virtual functions). Because programmers often benefit from understanding the mechanism of virtual functions in C++, this section will elaborate on the way the compiler implements this mechanism. The keyword virtual tells the compiler it should not perform early binding. Instead, it should automatically install all the mechanisms necessary to perform late binding. This means that if you call play( ) for a Brass object through an address for the base-class Instrument, you'll get the proper function. To accomplish this, the compiler creates a single table (called the VTABLE) for each class that contains virtual functions. The compiler places the addresses of the virtual functions for that particular class in the VTABLE. In each class with virtual functions, it secretly places a pointer, called the vpointer (abbreviated as VPTR), which points to the VTABLE for that object. All of this - setting up the VTABLE for each class, initializing the VPTR, inserting the code for the virtual function call - happens automatically, so you don't have to worry about it. With virtual functions, the proper function gets called for an object, even if the compiler cannot know the specific type of the object. The following sections go into this process in more detail. Storing type information You can see that there is no explicit type information stored in any of the classes. But the previous examples, and simple logic, tell you that there must be some sort of type information stored in the objects; otherwise the type could not be established at run-time. This is true, but the type information is hidden. With a single virtual function in OneVirtual, the size of the object is the size of NoVirtual plus the size of a void pointer. It turns out that the compiler inserts a single pointer (the VPTR) into the structure if you have one or more virtual functions. There is no size difference between OneVirtual and TwoVirtuals. That's because the VPTR points to a table of function addresses. You need only one because all the virtual function addresses are contained in that single table. This example required at least one data member. If there had been no data members, the C++ compiler would have forced the objects to be a nonzero size because each object must have a distinct address. If you imagine indexing into an array of zero-sized objects, you'll understand. A dummy member is inserted into objects that would otherwise be zero-sized. When the type information is inserted because of the virtual keyword, this takes the place of the dummy member. Try commenting out the int a in all the classes in the above example to see this. Picturing virtual functions To understand exactly what's going on when you use a virtual function, it's helpful to visualize the activities going on behind the curtain. Bookmark not defined.): The array of Instrument pointers has no specific type information; they each point to an object of type Instrument. However, the compiler doesn't know they are anything more than Instrument objects, so left to its own devices, it would normally call the base-class versions of all the functions. But in this case, all those functions have been declared with the virtual keyword, so something different happens. Each time you create a class that contains virtual functions, or you derive from a class that contains virtual functions, the compiler creates a VTABLE for that class, seen on the right of the diagram. In that table it places the addresses of all the functions that are declared virtual in this class or in the base class. If you don't redefine a function that was declared virtual in the base class, the compiler uses the address of the base-class version in the derived class. There is only one VPTR for each object when using simple inheritance like this. The VPTR must be initialized to point to the starting address of the appropriate VTABLE. But this self-knowledge is worthless unless it is used at the point a virtual function is called. When you call a virtual function through a base class address (the situation when the compiler doesn't have all the information necessary to perform early binding), something special happens. Instead of performing a typical function call, which is simply an assembly-language CALL to a particular address, the compiler generates different code to perform the function call. Here's what a call to adjust( ) for a Brass object it looks like, if made through an Instrument pointer. An Instrument reference produces the same result: The compiler starts with the Instrument pointer, which points to the starting address of the object. All Instrument objects or objects derived from Instrument have their VPTR in the same place (often at the beginning of the object), so the compiler can pick the VPTR out of the object. The VPTR points to the starting address of the VTABLE. All the VTABLEs are laid out in the same order, regardless of the specific type of the object. The compiler knows that regardless of the specific object type, the adjust( ) function is at the location VPTR+2. You send a message to the object, and the object figures out what to do with it. Under the hood It can be helpful to see the assembly-language code generated by a virtual function call, so you can see that late-binding is indeed taking place. At this point in the function, the register si (part of the Intel X86 processor architecture) contains the address of i. This is also pushed on the stack because it is the starting address of the object of interest. Remember that the starting address corresponds to the value of this, and this is quietly pushed on the stack as an argument before every member function call, so the member function knows which particular object it is working on. Thus you'll always see the number of arguments plus one pushed on the stack before a member function call (except for static member functions, which have no this). Now the actual virtual function call must be performed. First, the VPTR must be produced, so the VTABLE can be found. For this compiler the VPTR is inserted at the beginning of the object, so the contents of this correspond to the VPTR. It places the VPTR into the register bx. The VPTR contained in bx points to the starting address of the VTABLE, but the function pointer to call isn't at the zeroth location of the VTABLE, but instead the second location (because it's the third function in the list). For this memory model each function pointer is two bytes long, so the compiler adds four to the VPTR to calculate where the address of the proper function is. Note that this is a constant value, established at compile time, so the only thing that matters is that the function pointer at location number two is the one for adjust( ). Fortunately, the compiler takes care of all the bookkeeping for you and ensures that all the function pointers in all the VTABLEs occur in the same order. Once the address of the proper function pointer in the VTABLE is calculated, that function is called. In C and C++ assembly code you'll often see the caller clean off the arguments but this may vary depending on processors and compiler implementations. Installing the vpointer Because the VPTR determines the virtual function behavior of the object, you can see how it's critical that the VPTR always be pointing to the proper VTABLE. You don't ever want to be able to make a call to a virtual function before the VPTR is properly initialized. Of course, the place where initialization can be guaranteed is in the constructor, but none of the WIND examples has a constructor. This is where creation of the default constructor is essential. In the WIND examples, the compiler creates a default constructor that does nothing except initialize the VPTR. This constructor, of course, is automatically called for all Instrument objects before you can do anything with them, so you know that it's always safe to call virtual functions. The implications of the automatic initialization of the VPTR inside the constructor are discussed in a later section. Objects are different It's important to realize that upcasting deals only with addresses. If the compiler has an object, it knows the exact type and therefore (in C++) will not use late binding for any function calls - or at least, the compiler doesn't need to use late binding. For efficiency's sake, most compilers will perform early binding when they are making a call to a virtual function for an object because they know the exact type. When calling b3.f( ) there's no ambiguity. The compiler knows the exact type and that it's an object, so it can't possibly be an object derived from Base - it's exactly a Base. Thus early binding is probably used. However, if the compiler doesn't want to work so hard, it can still use late binding and the same behavior will occur. Why virtual functions? At this point you may have a question: If this technique is so important, and if it makes the 'right' function call all the time, why is it an option? This requires both code space and execution time. Some object-oriented languages have taken the approach that late binding is so intrinsic to object-oriented programming that it should always take place, that it should not be an option, and the user shouldn't have to know about it. This is a design decision when creating a language, and that particular path is appropriate for many languages.40 However, C++ comes from the C heritage, where efficiency is critical. After all, C was created to replace assembly language for the implementation of an operating system (thereby rendering that operating system - Unix - far more portable than its predecessors). So the virtual function is an option, and the language defaults to nonvirtual, which is the fastest configuration. Stroustrup stated that his guideline was If you don't use it, you don't pay for it. Thus the virtual keyword is provided for efficiency tuning. When designing your classes, however, you shouldn't be worrying about efficiency tuning. If you're going to use polymorphism, use virtual functions everywhere. You only need to look for functions to make non-virtual when looking for ways to speed up your code (and there are usually much bigger gains to be had in other areas). Anecdotal evidence suggests that the size and speed impacts of going to C++ are within 10% of the size and speed of C, and often much closer to the same. The reason you might get better size and speed efficiency is because you may design a C++ program in a smaller, faster way than you would using C. Abstract base classes and pure virtual functions In all the instrument examples, the functions in the base class Instrument were always dummy functions. If these functions are ever called, they indicate you've done something wrong. That's because the intent of Instrument is to create a common interface for all the classes derived from it, as seen on the diagram on the following page. The dashed lines indicate a class (a class is only a description and not a physical item - the dashed lines suggest its nonphysical nature), and the arrows from the derived classes to the base class indicate the inheritance relationship. The only reason to establish the common interface is so it can be expressed differently for each different subtype. It establishes a basic form, so you can say what's in common with all the derived classes. Nothing else. Another way of saying this is to call Instrument an abstract base class (or simply an abstract class). You create an abstract class when you want to manipulate a set of classes through this common interface. Notice you are only required to declare a function as virtual in the base class. All derived-class functions that match the signature of the base-class declaration will be called using the virtual mechanism. You can use the virtual keyword in the derived-class declarations (and some people do, for clarity), but it is redundant. If you have a genuine abstract class (like Instrument), objects of that class almost always have no meaning. That is, Instrument is meant to express only the interface, and not a particular implementation, so creating an Instrument object makes no sense, and you'll probably want to prevent the user from doing it. This can be accomplished by making all the virtual functions in Instrument print error messages, but this delays the information until run-time and requires reliable exhaustive testing on the part of the user. It is much better to catch the problem at compile time. C++ provides a mechanism for doing this called the pure virtual function. Here is the syntax used for a declaration: virtual void X() = 0; By doing this, you tell the compiler to reserve a slot for a function in the VTABLE, but not to put an address in that particular slot. If only one function in a class is declared as pure virtual, the VTABLE is incomplete. A class containing pure virtual functions is called a pure abstract base class. If the VTABLE for a class is incomplete, what is the compiler supposed to do when someone tries to make an object of that class? It cannot safely create an object of a pure abstract class, so you get an error message from the compiler if you try to make an object of a pure abstract class. Thus, the compiler ensures the purity of the abstract class, and you don't have to worry about misusing it. Here's WIND4.CPP (page Error! Note that pure virtual functions prevent a function call with the pure abstract class being passed in by value. Thus it is also a way to prevent object slicing from accidentally upcasting by value. This way you can ensure that a pointer or reference is always used during upcasting. Because one pure virtual function prevents the VTABLE from being generated doesn't mean you don't want function bodies for some of the others. Often you will want to call a base-class version of a function, even if it is virtual. It's always a good idea to put common code as close as possible to the root of your hierarchy. Not only does this save code space, it allows easy propagation of changes. Pure virtual definitions It's possible to provide a definition for a pure virtual function in the base class. You're still telling the compiler not to allow objects of that pure abstract base class, and the pure virtual functions must be defined in derived classes in order to create objects. However, there may be a piece of code you want some or all the derived class definitions to use in common, and you don't want to duplicate that code in every function. The other benefit to this feature is that it allows you to change to a pure virtual without disturbing the existing code. Inheritance and the VTABLE You can imagine what happens when you perform inheritance and redefine some of the virtual functions. The compiler creates a new VTABLE for your new class, and it inserts your new function addresses, using the base-class function addresses for any virtual functions you don't redefine. One way or another, there's always a full set of function addresses in the VTABLE, so you'll never be able to make a call to an address that isn't there (which would be disastrous). But what happens when you inherit and add new virtual functions in the derived class? A diagram will help visualize what's happening. Here are the VTABLEs created by the compiler for Base and Derived: Notice the compiler maps the location of the value address into exactly the same spot in the Derived VTABLE as it is in the Base VTABLE. Similarly, if a class is inherited from Derived, its version of shift would be placed in its VTABLE in exactly the same spot as it is in Derived. This is because (as you saw with the assembly-language example) the compiler generates code that uses a simple numerical offset into the VTABLE to select the virtual function. Regardless of what specific subtype the object belongs to, its VTABLE is laid out the same way, so calls to the virtual functions will always be made the same way. In this case, however, the compiler is working only with a pointer to a base-class object. The base class has only the value( ) function, so that is the only function the compiler will allow you to call. How could it possibly know that you are working with a Derived object, if it has only a pointer to a base-class object? That pointer might point to some other type, which doesn't have a shift function. It may or may not have some other function address at that point in the VTABLE, but in either case, making a virtual call to that VTABLE address is not what you want to do. So it's fortunate and logical that the compiler protects you from making virtual calls to functions that exist only in derived classes. There are some less-common cases where you may know that the pointer actually points to an object of a specific subclass. If you want to call a function that only exists in that subclass, then you must cast the pointer. If your problem is set up so that you must know the exact types of all objects, you should rethink it, because you're probably not using virtual functions properly. However, there are some situations where the design works best (or you have no choice) if you know the exact type of all objects kept in a generic container. This is the problem of run-time type identification (RTTI). Run-time type identification is all about casting base-class pointers down to derived-class pointers (up and down are relative to a typical class diagram, with the base class at the top). Casting up happens automatically, with no coercion, because it's completely safe. Casting down is unsafe because there's no compile time information about the actual types, so you must know exactly what type the object really is. If you cast it into the wrong type, you'll be in trouble. Chapter 17 describes the way C++ provides run-time type information. Object slicing There is a distinct difference between passing addresses and passing values when treating objects polymorphically. All the examples you've seen here, and virtually all the examples you should see, pass addresses and not values. This is because addresses all have the same size,42 so passing the address of an object of a derived type (which is usually bigger) is the same as passing the address of an object of the base type (which is usually smaller). As explained before, this is the goal when using polymorphism - code that manipulates objects of a base type can transparently manipulate derived-type objects as well. If you use an object instead of a pointer or reference as the recipient of your upcast, something will happen that may surprise you: the object is sliced until all that remains is the subobject that corresponds to your destination. It then calls the virtual function sum( ) for the Base object. In main( ), you might expect the first call to produce 10, and the second to produce 57. In fact, both calls produce 10. Two things are happening in this program. First, call( ) accepts only a Base object, so all the code inside the function body will manipulate only members associated with Base. Any calls to call( ) will cause an object the size of Base to be pushed on the stack and cleaned up after the call. This means that if an object of a class inherited from Base is passed to call( ), the compiler accepts it, but it copies only the Base portion of the object. It slices the derived portion off of the object, like this: Now you may wonder about the virtual function call. Here, the virtual function makes use of portions of both Base (which still exists) and Derived, which no longer exists because it was sliced off! So what happens when the virtual function is called? You're saved from disaster precisely because the object is being passed by value. Because of this, the compiler thinks it knows the precise type of the object (and it does, here, because any information that contributed extra features to the objects has been lost). In addition, when passing by value, it uses the copy-constructor for a Base object, which initializes the VPTR to the Base VTABLE and copies only the Base parts of the object. There's no explicit copy-constructor here, so the compiler synthesizes one. Under all interpretations, the object truly becomes a Base during slicing. Object slicing actually removes part of the object rather than simply changing the meaning of an address as when using a pointer or reference. Because of this, upcasting into an object is not often done; in fact, it's usually something to watch out for and prevent. You can explicitly prevent object slicing by putting pure virtual functions in the base class; this will cause a compile-time error message for an object slice. This must be done before there's any possibility of calling a virtual function. As you might guess, because the constructor has the job of bringing an object into existence, it is also the constructor's job to set up the VPTR. The compiler secretly inserts code into the beginning of the constructor that initializes the VPTR. In fact, even if you don't explicitly create a constructor for a class, the compiler will create one for you with the proper VPTR initialization code (if you have virtual functions). This has several implications. The first concerns efficiency. The reason for inline functions is to reduce the calling overhead for small functions. If C++ didn't provide inline functions, the preprocessor might be used to create these macros. However, the preprocessor has no concept of access or classes, and therefore couldn't be used to create member function macros. In addition, with constructors that must have hidden code inserted by the compiler, a preprocessor macro wouldn't work at all. You must be aware when hunting for efficiency holes that the compiler is inserting hidden code into your constructor function. Not only must it initialize the VPTR, it must also check the value of this (in case the operator new returns zero) and call base-class constructors. Taken together, this code can impact what you thought was a tiny inline function call. In particular, the size of the constructor can overwhelm the savings you get from reduced function-call overhead. If you make a lot of inline constructor calls, your code size can grow without any benefits in speed. Of course, you probably won't make all tiny constructors non-inline right away, because they're much easier to write as inlines. But when you're tuning your code, remember to remove inline constructors. Order of constructor calls The second interesting facet of constructors and virtual functions concerns the order of constructor calls and the way virtual calls are made within constructors. All base-class constructors are always called in the constructor for an inherited class. This makes sense because the constructor has a special job: to see that the object is built properly. A derived class has access only to its own members, and not those of the base class; only the base-class constructor can properly initialize its own elements. Therefore it's essential that all constructors get called; otherwise the entire object wouldn't be constructed properly. That's why the compiler enforces a constructor call for every portion of a derived class. It will call the default constructor if you don't explicitly call a base-class constructor in the constructor initializer list. If there is no default constructor, the compiler will complain. When you inherit, you know all about the base class and can access any public and protected members of the base class. This means you must be able to assume that all the members of the base class are valid when you're in the derived class. In a normal member function, construction has already taken place, so all the members of all parts of the object have been built. Inside the constructor, however, you must be able to assume that all members that you use have been built. The only way to guarantee this is for the base-class constructor to be called first. Then when you're in the derived-class constructor, all the members you can access in the base class have been initialized. Knowing all members are valid inside the constructor is also the reason that, whenever possible, you should initialize all member objects (that is, objects placed in the class using composition) in the constructor initializer list. If you follow this practice, you can assume that all base class members and member objects of the current object have been initialized. Behavior of virtual functions inside constructors The hierarchy of constructor calls brings up an interesting dilemma. What happens if you're inside a constructor and you call a virtual function? Inside an ordinary member function you can imagine what will happen - the virtual call is resolved at run-time because the object cannot know whether it belongs to the class the member function is in, or some class derived from it. For consistency, you might think this is what should happen inside constructors. This is not the case. If you call a virtual function inside a constructor, only the local version of the function is used. That is, the virtual mechanism doesn't work within the constructor. This behavior makes sense for two reasons. Conceptually, the constructor's job is to bring the object into existence (which is hardly an ordinary feat). Inside any constructor, the object may only be partially formed - you can only know that the base-class objects have been initialized, but you cannot know which classes are inherited from you. A virtual function call, however, reaches forward or outward into the inheritance hierarchy. It calls a function in a derived class. If you could do this inside a constructor, you'd be calling a function that might manipulate members that hadn't been initialized yet, a sure recipe for disaster. The second reason is a mechanical one. When a constructor is called, one of the first things it does is initialize its VPTR. However, it can only know that it is of the current type. The constructor code is completely ignorant of whether or not the object is in the base of another class. When the compiler generates code for that constructor, it generates code for a constructor of that class, not a base class and not a class derived from it (because a class can't know who inherits it). So the VPTR it uses must be for the VTABLE of that class. The VPTR remains initialized to that VTABLE for the rest of the object's lifetime unless this isn't the last constructor call. If a more-derived constructor is called afterwards, that constructor sets the VPTR to its VTABLE, and so on, until the last constructor finishes. The state of the VPTR is determined by the constructor that is called last. This is another reason why the constructors are called in order from base to most-derived. But while all this series of constructor calls is taking place, each constructor has set the VPTR to its own VTABLE. If it uses the virtual mechanism for function calls, it will produce only a call through its own VTABLE, not the most-derived VTABLE (as would be the case after all the constructors were called). In addition, many compilers recognize that a virtual function call is being made inside a constructor, and perform early binding because they know that late-binding will produce a call only to the local function. In either event, you won't get the results you might expect from a virtual function call inside a constructor. Destructors and virtual destructors Constructors cannot be made explicitly virtual (and the technique in Appendix B only simulates virtual constructors), but destructors can and often must be virtual. The constructor has the special job of putting an object together piece-by-piece, first by calling the base constructor, then the more derived constructors in order of inheritance. Similarly, the destructor also has a special job - it must disassemble an object that may belong to a hierarchy of classes. To do this, the compiler generates code that calls all the destructors, but in the reverse order that they are called by the constructor. That is, the destructor starts at the most-derived class and works its way down to the base class. This is the safe and desirable thing to do: The current destructor always knows that the base-class members are alive and active because it knows what it is derived from. Thus, the destructor can perform its own cleanup, then call the next-down destructor, which will perform its own cleanup, knowing what it is derived from, but not what is derived from it. You should keep in mind that constructors and destructors are the only places where this hierarchy of calls must happen (and thus the proper hierarchy is automatically generated by the compiler). In all other functions, only that function will be called, whether it's virtual or not. The only way for base-class versions of the same function to be called in ordinary functions (virtual or not) is if you explicitly call that function. Normally, the action of the destructor is quite adequate. But what happens if you want to manipulate an object through a pointer to its base class (that is, manipulate the object through its generic interface)? This is certainly a major objective in object-oriented programming. The problem occurs when you want to delete a pointer of this type for an object that has been created on the heap with new. If the pointer is to the base class, the compiler can only know to call the base-class version of the destructor during delete. Sound familiar? This is the same problem that virtual functions were created to solve for the general case. Fortunately virtual functions work for destructors as they do for all other functions except constructors. Even though the destructor, like the constructor, is an exceptional function, it is possible for the destructor to be virtual because the object already knows what type it is (whereas it doesn't during construction). Once an object has been constructed, its VPTR is initialized, so virtual function calls can take place. For a time, pure virtual destructors were legal and worked if you combined them with a function body, but in the final C++ standard function bodies combined with pure virtual functions were outlawed. This means that a virtual destructor cannot be pure, and must have a function body because (unlike ordinary functions) all destructors in a class hierarchy are always called. This way, you ensure against any surprises later. Virtuals in destructors There's something that happens during destruction that you might not immediately expect. If you're inside an ordinary member function and you call a virtual function, that function is called using the late-binding mechanism. This is not true with destructors, virtual or not. Inside a destructor, only the local version of the member function is called; the virtual mechanism is ignored. Why is this? Suppose the virtual mechanism were used inside the destructor. Then it would be possible for the virtual call to resolve to a function that was further out (more derived) on the inheritance hierarchy than the current destructor. But destructors are called from the outside in (from the most-derived destructor down to the base destructor), so the actual function called would rely on portions of an object that has already been destroyed! Thus, the compiler resolves the calls at compile-time and calls only the local version of the function. Notice that the same is true for the constructor (as described earlier), but in the constructor's case the information wasn't available, whereas in the destructor the information (that is, the VPTR) is there, but is isn't reliable. You've seen in this chapter that it's impossible to understand, or even create, an example of polymorphism without using data abstraction and inheritance. Polymorphism is a feature that cannot be viewed in isolation (like const or a switch statement, for example), but instead works only in concert, as part of a big picture of class relationships. People are often confused by other, non-object-oriented features of C++, like overloading and default arguments, which are sometimes presented as object-oriented. Don't be fooled: If it isn't late binding, it isn't polymorphism. Although this requires significant effort, it's a worthy struggle, because the results are faster program development, better code organization, extensible programs, and easier code maintenance. Polymorphism completes the object-oriented features of the language, but there are two more major features in C++: templates (Chapter 14), and exception handling (Chapter 16). These features provide you as much increase in programming power as each of the object-oriented features: abstract data typing, inheritance, and polymorphism. Exercises 1. Create a very simple shape hierarchy: a base class called shape and derived classes called circle, square, and triangle. In the base class, make a virtual function called draw( ), and redefine this in the derived classes. Create an array of pointers to shape objects you create on the heap (and thus perform upcasting of the pointers), and call draw( ) through the base-class pointers, to verify the behavior of the virtual function. If your debugger supports it, single-step through the example. 2. Modify Exercise 1 so draw( ) is a pure virtual function. Try creating an object of type shape. Try to call the pure virtual function inside the constructor and see what happens. Give draw( ) a definition. 3. Write a small program to show the difference between calling a virtual function inside a normal member function and calling a virtual function inside a constructor. The program should prove that the two calls produce different results. 4. In EARLY.CPP, how can you tell whether the compiler makes the call using early or late binding? Determine the case for your own compiler. 5. (Intermediate) Create a base class X with no members and no constructor, but with a virtual function. Create a class Y that inherits from X, but without an explicit constructor. Generate assembly code and examine it to determine if a constructor is created and called for X, and if so, what the code does. Explain what you discover. X has no default constructor, so why doesn't the compiler complain? 6. (Intermediate) Modify exercise 5 so each constructor calls a virtual function. Generate assembly code. Determine where the VPTR is being assigned inside each constructor. Is the virtual mechanism being used by your compiler inside the constructor? Establish why the local version of the function is still being called. 7. (Advanced) If function calls to an object passed by value weren't early-bound, a virtual call might access parts that didn't exist. Is this possible? Write some code to force a virtual call, and see if this causes a crash. To explain the behavior, examine what happens when you pass an object by value. 16: Introduction to templates Inheritance and composition provide a way to reuse object code. The template feature in C++ provides a way to reuse source code. Although C++ templates are a general-purpose programming tool, when they were introduced in the language, they seemed to discourage the use of object-based container-class hierarchies. Later versions of container-class libraries are built exclusively with templates and are much easier for the programmer to use. This chapter begins with an introduction to containers and the way they are implemented with templates, followed by examples of container classes and how to use them. Containers and iterators Suppose you want to create a stack. In C, you would make a data structure and associated functions, but of course in C++ you package the two together into an abstract data type. For simplicity it has been created here with a fixed size, but you can also modify it to automatically expand by allocating memory off the heap. Notice that iStackIter is a friend of iStack, which gives it access to all the private elements of iStack. Like a pointer, iStackIter's job is to move through an iStack and retrieve values. In this simple example, the iStackIter can move only forward (using both the pre- and postfix forms of the operator++) and it can fetch only values. However, there is no boundary to the way an iterator can be defined. It is perfectly acceptable for an iterator to move around any way within its associated container and to cause the contained values to be modified. However, it is customary that an iterator is created with a constructor that attaches it to a single container object and that it is not reattached during its lifetime. This is a fairly efficient implementation, because it never generates the numbers more than once. Once you have the classes built, they're quite simple to use. The need for containers Obviously an integer stack isn't a crucial tool. The real need for containers comes when you start making objects on the heap using new and destroying them with delete. In the general programming problem, you don't know how many objects you're going to need while you're writing the program. For example, in an air-traffic control system you don't want to limit the number of planes your system can handle. You don't want the program to abort just because you exceed some number. In a computer-aided design system, you're dealing with lots of shapes, but only the user determines (at run-time) exactly how many shapes you're going to need. Once you notice this tendency, you'll discover lots of examples in your own programming situations. C programmers who rely on virtual memory to handle their memory management often find the idea of new, delete, and container classes disturbing. Apparently, one practice in C is to create a huge global array, larger than anything the program would appear to need. This may not require much thought (or awareness of malloc( ) and free( )), but it produces programs that don't port well and can hide subtle bugs. In addition, if you create a huge global array of objects in C++, the constructor and destructor overhead can slow things down significantly. The C++ approach works much better: When you need an object, create it with new, and put its pointer in a container. Later on, fish it out and do something to it. This way, you create only the objects you absolutely need. And generally you don't have all the initialization conditions at the start-up of the program; you have to wait until something happens in the environment before you can actually create the object. So in the most common situation, you'll create a container that holds pointers to some objects of interest. You will create those objects using new and put the resulting pointer in the container (potentially upcasting it in the process), fishing it out later when you want to do something with the object. This technique produces the most flexible, general sort of program. Overview of templates Now a problem arises. You have an iStack, which holds integers. But you want a stack that holds shapes or airliners or plants or something else. Reinventing your source-code every time doesn't seem like a very intelligent approach with a language that touts reusability. There must be a better way. There are three techniques for source-code reuse: the C way, presented here for contrast; the Smalltalk approach, which significantly affected C++; and the C++ approach: templates. The C approach Of course you're trying to get away from the C approach because it's messy and error prone and completely inelegant. You copy the source code for a Stack and make modifications by hand, introducing new errors in the process. This is certainly not a very productive technique. The Smalltalk approach Smalltalk took a simple and straightforward approach: You want to reuse code, so use inheritance. To implement this, each container class holds items of the generic base class object. But, as mentioned before, the library in Smalltalk is of fundamental importance, so fundamental, in fact, that you don't ever create a class from scratch. Instead, you must always inherit it from an existing class. You find a class as close as possible to the one you want, inherit from it, and make a few changes. Obviously this is a benefit because it minimizes your effort (and explains why you spend a lot of time learning the class library before becoming an effective Smalltalk programmer). But it also means that all classes in Smalltalk end up being part of a single inheritance tree. You must inherit from a branch of this tree when creating a new class. Most of the tree is already there (it's the Smalltalk class library), and at the root of the tree is a class called object - the same class that each Smalltalk container holds. This is a neat trick because it means that every class in the Smalltalk class hierarchy is derived from object, so every class can be held in every container, including that container itself. It just means a class tree with object (or some similar name) at its root and container classes that hold object. In the process of trying to use the container classes, they discovered a problem. The problem was that in Smalltalk, you could force people to derive everything from a single hierarchy, but in C++ you can't. You might have your nice object-based hierarchy with its container classes, but then you might buy a set of shape classes or airline classes from another vendor who didn't use that hierarchy. Here's what the problem looks like: Because C++ supports multiple independent hierarchies, Smalltalk's object-based hierarchy does not work so well. The solution seemed obvious. If you can have many inheritance hierarchies, then you should be able to inherit from more than one class: Multiple inheritance will solve the problem. So you do the following: Now oshape has shape's characteristics and behaviors, but because it is also derived from object it can be placed in container. But multiple inheritance wasn't originally part of C++. Once the container problem was seen, there came a great deal of pressure to add the feature. Other programmers felt (and still feel) multiple inheritance wasn't a good idea and that it adds unneeded complexity to the language. Compiler vendors followed suit by including object-based container-class hierarchies, most of which have since been replaced by template versions. You can argue that multiple inheritance is needed for solving general programming problems, but you'll see in the next chapter that its complexity is best avoided except in special cases. The template approach Although an object-based hierarchy with multiple inheritance is conceptually straightforward, it turns out to be painful to use. In his original book45 Stroustrup demonstrated what he considered a preferable alternative to the object-based hierarchy. Container classes were created as large preprocessor macros with arguments that could be substituted for your desired type. When you wanted to create a container to hold a particular type, you made a couple of macro calls. Unfortunately, this approach was confused by all the existing Smalltalk literature, and it was a bit unwieldy. Basically, nobody got it. In the meantime, Stroustrup and the C++ team at Bell Labs had modified his original macro approach, simplifying it and moving it from the domain of the preprocessor into the compiler itself. This new code-substitution device is called a template46, and it represents a completely different way to reuse code: Instead of reusing object code, as with inheritance and composition, a template reuses source code. The container no longer holds a generic base class called object, but instead an unspecified parameter. When you use a template, the parameter is substituted by the compiler, much like the old macro approach, but cleaner and easier to use. In C++, the template implements the concept of a parameterized type. Another benefit of the template approach is that the novice programmer who may be unfamiliar or uncomfortable with inheritance can still use canned container classes right away. Template syntax The template keyword tells the compiler that the following class definition will manipulate one or more unspecified types. At the time the object is defined, those types must be specified so the compiler can substitute them. Also, you see T used everywhere in the class where you would normally see the specific type the container holds. Notice that if the index is out of bounds, the Standard C library macro assert( ) is used to print a message (assert( ) is used instead of require( ) because you'll probably want to completely remove the test code once it's debugged). This is actually a case where throwing an exception is more appropriate, because then the class user can recover from the error, but that topic is not covered until Chapter 16. In main( ), you can see how easy it is to create Arrays that hold different types of objects. Also note that duplicate class definitions are either avoided by the compiler or merged by the linker. Non-inline function definitions Of course, there are times when you'll want to have non-inline member function definitions. In this case, the compiler needs to see the template declaration before the member function definition. Header files Even if you create non-inline function definitions, you'll generally want to put all declarations and definitions for a template in a header file. This may seem to violate the normal header file rule of Don't put in anything that allocates storage to prevent multiple definition errors at link time, but template definitions are special. So you'll almost always put the entire template declaration and definition in the header file, for ease of use. There are times when you may need to place the template definitions in a separate CPP file to satisfy special needs (for example, forcing template instantiations to exist in only a single Windows DLL file). Most compilers have some mechanism to allow this; you'll have to investigate your particular compiler's documentation to use it. You can imagine that internally, the arguments in the template argument list are also being mangled to produce a unique class name for each template instantiation. Also notice that a template makes certain assumptions about the objects it is holding. For example, Stackt assumes there is some sort of assignment operation for T inside the push( ) function. You could say that a template implies an interface for the types it is capable of holding. Constants in templates Template arguments are not restricted to class types; you can also use built-in types. The values of these arguments then become compile-time constants for that particular instantiation of the template. This pointer is not initialized in the constructor; the initialization is delayed until the first access. You might use a technique like this if you are creating a lot of objects, but not accessing them all, and want to save storage. Stash and stack as templates It turns out that the Stash and Stack classes that have been updated periodically throughout this book are actually container classes, so it makes sense to convert them to templates. But first, one other important issue arises with container classes: When a container releases a pointer to an object, does it destroy that object? For example, when a container object goes out of scope, does it destroy all the objects it points to? The ownership problem This issue is commonly referred to as ownership. Containers that hold entire objects don't usually worry about ownership because they clearly own the objects they contain. To prevent this from happening, you must consider ownership when designing and using a container. Many programs are very simple, and one container holds pointers to objects that are used only by that container. In this case ownership is very straightforward: The container owns its objects. Generally, you'll want this to be the default case for a container because it's the most common situation. The best approach to handling the ownership problem is to give the client programmer the choice. This is often accomplished by a constructor argument that defaults to indicating ownership (typically desired for simple programs). In addition there may be read and set functions to view and modify the ownership of the container. If the container has functions to remove an object, the ownership state usually affects that removal, so you may also find options to control destruction in the removal function. Stash as a template The stash class that has been evolving throughout the book (last seen in Chapter 11) is an ideal candidate for a template. Here it's more convenient to use, but you can try moving it if you want to see the effect. The storage pointer is made protected so inherited classes can directly access it. This means that the inherited classes may become dependent on the specific implementation of TStash, but as you'll see in the SORTED.CPP example, it's worth it. The own flag indicates whether the container defaults to owning its objects. If so, in the destructor each object whose pointer is in the container is destroyed. This is straightforward; the container knows the type it contains. You can also change the default ownership in the constructor or read and modify it with the overloaded Owns( ) function. You should be aware that if the container holds pointers to a base-class type, that type should have a virtual destructor to ensure proper cleanup of derived objects whose addresses have been upcast when placing them in the container. The TStashIter follows the iterator model of bonding to a single container object for its lifetime. In addition, the copy-constructor allows you to make a new iterator pointing at the same location as the existing iterator you create it from, effectively making a bookmark into the container. The forward( ) and backward( ) member functions allow you to jump an iterator by a number of spots, respecting the boundaries of the container. The overloaded increment and decrement operators move the iterator by one place. The smart pointer is used to operate on the element the iterator is referring to, and remove( ) destroys the current object by calling the container's remove( ). The following example creates and tests two different kinds of Stash objects, one for a new class called Int that announces its construction and destruction and one that holds objects of the class String from Chapter 11. Notice the elegance produced by using these constructs: You aren't assailed with the implementation details of using an array. You tell the container and iterator objects what to do, not how. This makes the solution easier to conceptualize, to build, and to modify. The iterator is very simple and very small - the size of a single pointer. When you create a TStackIterator, it's initialized to the head of the linked list, and you can only increment it forward through the list. If you want to start over at the beginning, you create a new iterator, and if you want to remember a spot in the list, you create a new iterator from the existing iterator pointing at that spot (using the copy-constructor). Finally, the operator int indicates whether or not you are at the end of the list and allows the iterator to be used in conditional statements. The entire implementation is contained in the header file, so there's no separate CPP file. Then an iterator is created and used to move through the linked list. The tenth line is remembered by copy-constructing a second iterator from the first; later this line is printed and the iterator - created dynamically - is destroyed. Here, dynamic object creation is used to control the lifetime of the object. This is very similar to earlier test examples for the Stack class, but now the contained objects are properly destroyed when the TStack is destroyed. In addition, this class uses templates to add a special feature: you can decide, when you instantiate the SString, whether it lives on the stack or the heap. But if you're concerned about the efficiency of creating and destroying a lot of strings, you can take a chance and assume the largest word size possible for the solution of your problem. When you give the template a size argument, it automatically creates the object totally on the stack rather than on the heap, which means the overhead of one new and one delete per object is eliminated. You can see that operator= is also speeded up. The comparison operators for the string use a function called stricmp( ), which is not Standard C but which nonetheless is available with most compiler libraries. It performs a string compare while ignoring the case of the letters. Templates and inheritance There's nothing to prevent you from using a class template in any way you'd use an ordinary class. For example, you can easily inherit from a template, and you can create a new template that instantiates and inherits from an existing template. The uniqueness of Urand is produced by keeping a map of all the numbers possible in the random space (the upper bound is set with the template argument) and marking each one off as it's used. The optional second constructor argument allows you to reuse the numbers once they're all used up. Notice that this implementation is optimized for speed by allocating the entire map, regardless of how many numbers you're going to need. If you want to optimize for size, you can change the underlying implementation so it allocates storage for the map dynamically and puts the random numbers themselves in the map rather than flags. Notice that this change in implementation will not affect any client code. When a template provides more functionality for you, the trade-off is usually that it puts more requirements on your class. Sometimes you'll have to inherit the contained class to add the required functionality. Notice the value of using an overloaded operator here - the Integer class can rely on its underlying implementation to provide the functionality. In this example you can see the usefulness of making the underlying storage in TStash protected rather than private. That way you can still change the underlying implementation without propagating modifications. Design and efficiency In Sorted, every time you call add( ) the element is inserted and the array is resorted. Here, the horribly inefficient and greatly deprecated (but easy to understand and code) bubble sort is used. This is perfectly appropriate, because it's part of the private implementation. During program development, your priorities are to 1. Get the class interfaces correct. 2. Create an accurate implementation as rapidly as possible so you can. 3. Prove your design. Very often, you will discover problems with the class interface only when you assemble your initial rough draft of the working system. You may also discover the need for helper classes like containers and iterators during system assembly and during your first-pass implementation. Sometimes it's very difficult to discover these kinds of issues during analysis - your goal in analysis should be to get a big-picture design that can be rapidly implemented and tested. Only after the design has been proven should you spend the time to flesh it out completely and worry about performance issues. If the design fails, or if performance is not a problem, the bubble sort is good enough, and you haven't wasted any time. If some of the functionality of a template does not depend on type, it can be put in a common base class to prevent needless reproduction of that code. For example, in Chapter 12 in INHSTAK.CPP (page Error! Bookmark not defined.) inheritance was used to specify the types that a Stack could accept and produce. However, the ownership problem has been solved here by adding a destructor (which is type-dependent, and thus must be created by the template). Here, it defaults to ownership. Notice that when the base-class destructor is called, the stack will be empty so no duplicate releases will occur. Polymorphism and containers It's common to see polymorphism, dynamic object creation and containers used together in a true object-oriented program. The following example is a little simulation of trash recycling. All the trash is put into a single bin, then later it's sorted out into separate bins. There's a function that goes through any trash bin and figures out what the resource value is. The container TStack is instantiated for Trash, so it holds Trash pointers, which are pointers to the base class. However, it will also hold pointers to objects of classes derived from Trash, as you can see in the call to push( ). When these pointers are added, they lose their specific identities and become simply Trash pointers (they are upcast). However, because of polymorphism the proper behavior still occurs when the virtual function is called through the tally and sorter iterators. When the bin container goes out of scope, the container's destructor calls all the virtual destructors for the objects it contains, and thus properly cleans everything up. Because container class templates are rarely subject to the inheritance and upcasting you see with ordinary classes, you'll almost never see virtual functions in these types of classes. Their reuse is implemented with templates, not with inheritance. Function templates A class template describes an infinite set of classes, and the most common place you'll see templates is with classes. However, C++ also supports the concept of an infinite set of functions, which is sometimes useful. The syntax is virtually identical, except that you create a function instead of a class. The clue that you should create a function template is, as you might suspect, if you find you're creating a number of functions that look identical except that they are dealing with different types. The classic example of a function template is a sorting function.47 However, a function template is useful in all sorts of places, as demonstrated in the first example that follows. The second example shows a function template used with containers and iterators. A memory allocation system There's a few things you can do to make the raw memory allocation routines malloc( ), calloc( ) and realloc( ) safer. The following function template produces one function getmem( ) that either allocates a new piece of memory or resizes an existing piece (like realloc( )). In addition, it zeroes only the new memory, and it checks to see that the memory is successfully allocated. Also, you only tell it the number of elements of the type you want, not the number of bytes, so the possibility of a programmer error is reduced. The typedef cntr is the type of this counter; it allows you to change from int to long if you need to handle larger chunks (other issues come up when using long, however - these are seen in compiler warnings). A pointer reference is used for the argument oldmem because the outside variable (a pointer) must be changed to point to the new block of memory. This function assumes you're using it properly, but for debugging you could add an additional tag next to the counter containing an identifier, and check that identifier in getmem( ) to help discover incorrect calls. If the number of elements requested is zero, the storage is freed. There's an additional function template freemem( ) that aliases this behavior. You'll notice that getmem( ) is very low-level - there are lots of casts and byte manipulations. For example, the oldmem pointer doesn't point to the true beginning of the memory block, but just past the beginning to allow for the counter. So to free( ) the memory block, getmem( ) must back up the pointer by the amount of space occupied by cntr. Because oldmem is a T*, it must first be cast to a cntr*, then indexed backwards one place. The true starting address is required inside realloc( ). If the storage size is being increased, then the difference between the new number of elements and the old number is used to calculate the starting address and the amount of memory to zero in memset( ). Here's a program to test getmem( ). Notice that a different version of getmem( ) is instantiated for the int and float pointers. You might think that because all the manipulations are so low-level you could get away with a single non-template function and pass a void*and as oldmem. This doesn't work because then the compiler must do a conversion from your type to a void*. To take the reference, it makes a temporary. This produces an error because then you're modifying the temporary pointer, not the pointer you want to change. So the function template is necessary to produce the exact type for the argument. Applying a function to a TStack Suppose you want to take a TStack and apply a function to all the objects it contains. It uses an iterator to move through the Stack and apply the function to every object. If you've (understandably) forgotten the pointer-to-member syntax, you can refresh your memory at the end of Chapter 9. You can see there is more than one version of applist( ), so it's possible to overload function templates. Member function templates It's also possible to make applist( ) a member function template of the class. That is, a separate template definition from the class' template, and yet a member of the class. Explicit instantiation is only for special cases where extra control is needed. Template specialization The Sorted vector only works with objects of user-defined types. It won't instantiate properly to sort an array of char*, for example. To create a special version you write the instantiation yourself as if the compiler had gone through and substituted your type(s) for the template argument(s). But you put your own code in the function bodies of the specialization. In addition, they provide a great deal of safety and flexibility by replacing the primitive arrays and relatively crude data structure techniques found in C. Because the client programmer needs containers, it's essential that they be easy to use. This is where the template comes in. With templates the syntax for source-code reuse (as opposed to object-code reuse provided by inheritance and composition) becomes trivial enough for the novice user. In fact, reusing code with templates is notably easier than inheritance and composition. The issues involved with container-class design have been touched upon in this chapter, but you may have gathered that they can go much further. A complicated container-class library may cover all sorts of additional issues, including persistence (introduced in Chapter 15) and garbage collection (introduced in Chapter 11), as well as additional ways to handle the ownership problem. Exercises 1. Modify the result of Exercise 1 from Chapter 13 to use a TStack and TStackIterator instead of an array of shape pointers. Add destructors to the class hierarchy so you can see that the shape objects are destroyed when the TStack goes out of scope. 2. Modify the SSHAPE2.CPP example from Chapter 13 to use TStack instead of an array. 3. Modify RECYCLE.CPP to use a TStash instead of a TStack. 4. Change SETTEST.CPP to use a SortedSet instead of a set. 5. Duplicate the functionality of APPLIST.CPP for the TStash class. 6. You can do this exercise only if your compiler supports member function templates. Copy TSTACK.H to a new header file and add the function templates in APPLIST.CPP as member function templates of TStack. 7. (Advanced) Modify the TStack class to further increase the granularity of ownership: add a flag to each link indicating whether that link owns the object it points to, and support this information in the add( ) function and destructor. Add member functions to read and change the ownership for each link, and decide what the owns flag means in this new context. 8. (Advanced) Modify the TStack class so each entry contains reference-counting information (not the objects they contain), and add member functions to support the reference counting behavior. 9. (Advanced) Change the underlying implementation of Urand in SORTED.CPP so it is space-efficient (as described in the paragraph following SORTED.CPP) rather than time-efficient. This is a pointer arithmetic problem. Part 2: The Standard C++ Library 17: Library Overview Standard C++ not only incorporates all the Standard C libraries, with small additions and changes to support type safety, it also adds libraries of its own. Somewhat more readable (and yet still a self-described expert's guide) is Stroustrup's 3rd Edition of The C++ Programming Language. (Addison-Wesley, 1998??). However, there are some techniques and topics that are used rarely enough that they are not covered here, so if you can't find it in these chapters you should reach for Stroustrup. The iostream library was introduced earlier in this book (see Chapter 5). Other useful libraries in Standard C++ include the following: Language Support. Diagnostics Library. Components C++ programs can use to detect and report errors. These components are used by other parts of the Standard C++ library, but you can also use them in your own programs. Strings Library. The string class may be the most thorough string manipulation tool you've ever seen. In addition, there's a wide wstring class designed to support international character sets. Note that the string classes are seamlessly integrated with iostreams, virtually eliminating the need for you to ever use strstream (or worry about the associated memory-management gotchas described in Chapter 5). The string class will be covered in detail in Chapter XX. Localization Library. This allows you to localize strings in your program to adapt to usage in different countries, including money, numbers, date, time, and so on. Containers Library. Both bits and bit_string are more complete implementations of the bitvector concept introduced in Chapter 4 (see page Error! Bookmark not defined.). The bits template creates a fixed-sized array of bits that can be manipulated with all the bitwise operators, as well as member functions like set( ), reset( ), count( ), length( ), test( ), any( ), and none( ). There are also conversion operators to_ushort( ), to_ulong( ), and to_string( ). The bit_string class is, by contrast, a dynamically sized array of bits, with similar operations to bits, but also with additional operations that make it act somewhat like a string. There's a fundamental difference in bit weighting: With bits, the right-most bit (bit zero) is the least significant bit, but with bit_string, the right-most bit is the most significant bit. There are no conversions between bits and bit_string. You'll use bits for a space-efficient set of on-off flags and bit_string for manipulating arrays of binary values (like pixels). Iterators Library. Includes iterators that are tools for the STL (described in the next section of this appendix), streams, and stream buffers. Algorithms Library. These are the template functions that perform operations on the STL containers using iterators. Numerics Library. The goal of this library is to allow the compiler implementor to take advantage of the architecture of the underlying machine when used for numerical operations. This way, creators of higher level numerical libraries can write to the numerics library and produce efficient algorithms without having to customize to every possible machine. The numerics library also includes the complex number class (which appeared in the first version of C++ as an example, and has become an expected part of the library) in float, double, and long double forms. Summary 18: Strings 52This chapter examines C++ Standard string class, beginning with a look at what constitutes a C++ string and how the C++ version differs from a traditional C string. You'll learn about operations and manipulations using string objects, and see how C++ strings accommodate variation in character sets and string data conversion. Handling text is perhaps one of the oldest of all programming applications, so it's not surprising that the C++ string draws heavily on the ideas and terminology that have long been used for this purpose in C and other languages. You'll see how each of these jobs is accomplished using C++ string objects. What's in a string In C, a string is simply an array of characters that always includes a binary zero (often called the null terminator) as its final array element. There are two significant differences between C++ strings and their C progenitors. First, C++ string objects associate the array of characters which constitute the string with methods useful for managing and operating on it. A string also contains certain housekeeping information about the size and storage location of its data. Specifically, a C++ string object knows its starting location in memory, its content, its length in characters, and the length in characters to which it can grow before the string object must resize its internal data buffer. This gives rise to the second big difference between C char arrays and C++ strings. C++ strings do not include a null terminator, nor do the C++ string handling member functions rely on the existence of a null terminator to perform their jobs. The exact implementation of memory layout for the string class is not defined by the C++ Standard. This architecture is intended to be flexible enough to allow differing implementations by compiler vendors, yet guarantee predictable behavior for users. In particular, the exact conditions under which storage is allocated to hold data for a string object are not defined in the C++ Standard. String allocation rules were formulated to allow but not require a reference-counted implementation, but whether or not the implementation uses reference counting, the semantics must be the same. To put this a bit differently, in C, every char array occupies a unique physical region of memory. Creating and initializing C++ strings Creating and initializing strings is a straightforward proposition, and fairly flexible as well. In the example shown below, the first string, imBlank, is declared but contains no initial value. Unlike a C char array, which would contain a random and meaningless bit pattern until initialization, imBlank does contain meaningful information. This string object has been initialized to hold no characters, and can properly report its 0 length and absence of data elements through the use of class member functions. The next string, heyMom, is initialized by the literal argument Where are my socks?. This form of initialization uses a quoted character array as a parameter to the string constructor. By contrast, standardReply is simply initialized with an assignment. The last string of the group, useThisOneAgain, is initialized using an existing C++ string object. Both of these arguments have default values and if you say substr( ) with an empty argument list you produce a copy of the entire string, so this is a convenient way to duplicate a string. Here's what the string quoteMe contains after the initialization shown above : What is that one clam doing with Elvis in a UFO? Notice the final line of example above. C++ allows string initialization techniques to be mixed in a single statement, a flexible and convenient feature. Also note that the last initializer copies just one character from the source string. Another slightly more subtle initialization technique involves the use of the string iterators string.begin( ) and string.end( ). Initialization limitations C++ strings may not be initialized with single characters or with ASCII or other integer values. Operating on strings If you've programmed in C, you are accustomed to the convenience of a large family of functions for writing, searching, rearranging, and copying char arrays. However, there are two unfortunate aspects of the Standard C library functions for handling char arrays. The second inherent trap of the standard C char array tools is that they all rely explicitly on the assumption that the character array includes a null terminator. If by oversight or error the null is omitted or overwritten, there's very little to keep the C char array handling functions from manipulating the memory beyond the limits of the allocated space, sometimes with disastrous results. C++ provides a vast improvement in the convenience and safety of string objects. For purposes of actual string handling operations, there are a modest two or three dozen member function names. It's worth your while to become acquainted with these. Each function is overloaded, so you don't have to learn a new string member function name simply because of small differences in their parameters. Appending, inserting and concatenating strings One of the most valuable and convenient aspects of C++ strings is that they grow as needed, without intervention on the part of the programmer. Not only does this make string handling code inherently more trustworthy, it also almost entirely eliminates a tedious housekeeping chore - keeping track of the bounds of the storage in which your strings live. For example, if you create a string object and initialize it with a string of 50 copies of 'X', and later store in it 50 copies of Zowie, the object itself will reallocate sufficient storage to accommodate the growth of the data. Perhaps nowhere is this property more appreciated than when the strings manipulated in your code will change in size, but when you don't know big the change is. Appending, concatenating, and inserting strings often give rise to this circumstance, but the string member functions append( ) and insert( ) transparently reallocate storage when a string grows. Size = 21 Capacity = 31 I thought I saw Elvis in a UFO. Size = 32 Capacity = 63 I thought I saw Elvis in a UFO. I have been working too hard. The size( ), resize( ), capacity( ), and reserve( ) member functions can be very useful when its necessary to work back and forth between data contained in C++ style strings and traditional null terminated C char arrays. Note the ease with which we changed the size of the storage allocated to the string. The exact fashion in which the string member functions will allocate space for your data is dependent on the implementation of the library. When one implementation was tested with the example above, it appeared that reallocations occurred on even word boundaries, with one byte held back. Space grows and existing characters politely move over to accommodate the new elements. Sometimes, however, this might not be what you want to happen. If we add the following fragment of code to StrSize.cpp, we can test replace( ). I have been working too hard. If replace doesn't find the search string, it returns npos. Unlike insert( ), replace( ) won't grow the string's storage space if you copy new characters into the middle of an existing series of array elements. However, it will grow the storage space if you make a replacement that writes beyond the end of an existing array. The output looks like this: I saw Elvis in a wig. I have been working too hard.wig Notice that replace( ) expands the array to accommodate the growth of the string due to replacement beyond the bounds of the existing array. On the right hand side of the statement, you can use almost any type that evaluates to a group of one or more characters. Searching in strings The find family of string member functions allows you to locate a character or group of characters within a given string. If no match is found, it returns npos. If no match is found, it returns npos. If no such element is found, it returns npos. If no such element is found, it returns npos. If no match is found, it returns npos. String searching member functions and their general uses The simplest use of find( ) searches for one or more characters in a string. This overloaded version of find( ) takes a parameter that specifies the character(s) for which to search, and optionally one that tells it where in the string to begin searching for the occurrence of a substring. Notice that we define the string object sieveChars using a constructor idiom which sets the initial size of the character array and writes the value 'P' to each of its member. Notice that find passed over the Een group of characters in the word Eenie. The find member function performs a case sensitive search. There are no functions in the string class to change the case of a string, but these functions can be easily created using the Standard C library functions toupper( ) and tolower( ), which change the case of one character at a time. Then they create a new string with the new data, release the buffer and return the result string. The c_str( ) function cannot be used to produce a pointer to directly manipulate the data in the string because c_str( ) returns a pointer to const. That is, you're not allowed to manipulate string data with a pointer, only with member functions. If you need to use the more primitive char array manipulation, you should use the technique shown above. The output looks like this: Eenie, meenie, miney, mo eenie, meenie, miney, mo EENIE, MEENIE, MINEY, MO 8 eenie, meenie, miney, mo 0 8 EENIE, MEENIE, MINEY, MO 0 8 The case insensitive searches found both occurrences on the een group. NewFind.cpp isn't the best solution to the case sensitivity problem, so we'll revisit it when we examine string comparisons. The string member function rfind( ) handles this job. This means that all the indentation in the code listings is lost. In addition, Word saves HTML with reduced font sizes for body text, which makes it hard to read. Removal is accomplished with the erase( ) member function, but you must correctly determine the starting and ending points of the substring you wish to erase. These tags are treated in a special way by the logic in the Extractcode.cpp tool for extracting code listings. To allow us to present the code for the tool in the text of the book, we had to make sure that the tag sequence itself didn't occur in the listing. To do so , we took advantage of a C++ preprocessor feature that causes text strings delimited by adjacent pairs of double quotes to be merged into a single string during the preprocessor pass of the build. Comparing strings Comparing strings is inherently different than comparing numbers. Numbers have constant, universally meaningful values. To evaluate the relationship between the magnitude of two strings, you must make a lexical comparison. Most often, this will be the ASCII collating sequence, which assigns the printable characters for the English language numbers in the range from 32 to 127 decimal. In the ASCII collating sequence, the first character in the list is the space, followed by several common punctuation marks, and then uppercase and lowercase letters. With respect to the alphabet, this means that the letters nearer the front have lower ASCII values than those nearer the end. C++ provides several ways to compare strings, and each has their advantages. Notice in the code fragment below the flexibility of argument types on both the left and right hand side of the comparison operators. The overloaded operator set allows the direct comparison of string objects, quoted literals, and pointers to C style strings. It provides overloaded versions that allow you to compare two complete strings, part of either string to a complete string, and subsets of two strings. By catching this object in an exception handler, you can take appropriate remedial actions such as recalculating the offending subscript or growing the array. Sometimes the quick and dirty method is justifiable, but in general, you won't want to sacrifice the advantages of having your string data safely and securely encapsulated in the C++ object where it lives. Here is a better, safer way to handle case insensitive comparison of two C++ string objects. Because no data is copied out of the objects and into C style strings, you don't have to use pointers and you don't have to risk overwriting the bounds of an ordinary character array. In this example, we use the string iterator. Iterators are themselves objects which move through a collection or container of other objects, selecting them one at a time, but never providing direct access to the implementation of the container. Iterators are not pointers, but they are useful for many of the same jobs. Return 0 if the same. In fact, it can't. This definition of p1 tells us that we can only use the elements p1 points to as constants. This means that the string didn't have to be copied or rewritten to accommodate case insensitive comparison. Both of the strings retain their original data, unmodified. Iterating in reverse Just as the standard C pointer gives us the increment (++) and decrement (--) operators to make pointer arithmetic a bit more convenient, C++ string iterators come in two basic varieties. You've seen end( ) and begin( ), which are the tools for moving forward through a string one element at a time. The reverse iterators rend( ) and rbegin( ) allow you to step backwards through a string. Aside from this, the main thing to remember about reverse iterators is that they aren't type equivalent to ordinary iterators. For example, if a member function parameter list includes an iterator as an argument, you can't substitute a reverse iterator to get the function to perform it's job walking backward through the string. This is also true of string members such as copy( ), insert( ), and assign( ). Consider what it means for a character to have case. Written Hebrew, Farsi, and Kanji don't use the concept of upper and lower case, so for those languages this idea has no meaning at all. This the first impediment to built-in C++ support for case-insensitive character search and comparison: the idea of case sensitivity is not universal, and therefore not portable. It would seem that if there were a way of designating that some languages were all uppercase or all lowercase we could design a generalized solution. However, some languages which employ the concept of case also change the meaning of particular characters with diacritical marks: the cedilla in Spanish, the circumflex in French, and the umlaut in German. For this reason, any case-sensitive collating scheme that attempts to be comprehensive will be nightmarishly complex to use. Although we usually treat the C++ string as a class, this is really not the case. The main thing to notice about the two declarations above are that the string type is created when the basic_string template is instantiated with char. This is why the string class doesn't include case insensitive member functions: That's not in its job description. This information can be used to make a new type of string class that ignores case. Next, we'll override only the members we need to change in order to make character-by-character comparison case insensitive. This is just a simple example - in order to make istring fully equivalent to string, we'd have to create the other functions necessary to support the new istring type. Summary C++ string objects provide developers with a number of great advantages over their C counterparts. For the most part, the string class makes referring to strings through the use of character pointers unnecessary. This eliminates an entire class of software defects that arise from the use of uninitialized and incorrectly valued pointers. C++ strings dynamically and transparently grow their internal data storage space to accommodate increases in the size of the string data. This means that when the data in a string grows beyond the limits of the memory initially allocated to it, the string object will make the memory management calls that take space from and return space to the heap. Consistent allocation schemes prevent memory leaks and have the potential to be much more efficient than roll your own memory management. The string class member functions provide a fairly comprehensive set of tools for creating, modifying, and searching in strings. Exercises 1. A palindrome is a word or group of words that read the same forward and backward. For example madam or wow. Write a program that takes a string argument and and returns TRUE if the string was a palindrome. 2. Some times the input from a file stream contains a two character sequence to represent a newline. These two characters (0x0a 0x0d) produce extra blank lines when the stream is printed to standard out. Write a program that finds the character 0x0d (ASCII carraige return) and deletes it from the string. 3. Write a program that reverses the order of the characters in a string. That's the idea behind iostreams. They're much easier, safer, and often more efficient than the assorted functions from the Standard C stdio library. Iostream is usually the first class library that new C++ programmers learn to use. In future chapters, you'll see how to set up your own classes so they're compatible with iostreams. Why iostreams? You may wonder what's wrong with the good old C library. And why not wrap the C library in a class and be done with it? Indeed, there are situations when this is the perfect thing to do, when you want to make a C library a bit safer and easier to use. If there's a failure, the name of the file is printed and exit( ) is called. The destructor closes the file, and the access function fp( )returns f. When you're done with it, just forget about it, and the file is closed by the destructor at the end of the scope. True wrapping Even though the FILE pointer is private, it isn't particularly safe because fp( ) retrieves it. The only effect seems to be guaranteed initialization and cleanup, so why not make it public, or use a struct instead? Notice that while you can get a copy of f using fp( ), you cannot assign to f - that's completely under the control of the class. Of course, after capturing the pointer returned by fp( ), the client programmer can still assign to the structure elements, so the safety is in guaranteeing a valid FILE pointer rather than proper contents of the structure. If you want complete safety, you have to prevent the user from direct access to the FILE pointer. File has the same constructor as in the previous example, and it also has a default constructor. The default constructor sets the private FILE pointer f to zero. But now, before any reference to f, its value must be checked to ensure it isn't zero. This is accomplished with the last member function in the class, F( ), which is private because it is intended to be used only by other member functions. The big stumbling block is the run-time interpreter used for the variable-argument list functions. This is the code that parses through your format string at run-time and grabs and interprets arguments from the variable argument list. It's a problem for four reasons. 1. Even if you use only a fraction of the functionality of the interpreter, the whole thing gets loaded. So if you say: printf(%c, 'x'); you'll get the whole package, including the parts that print out floating-point numbers and strings. There's no option for reducing the amount of space used by the program. 2. Because the interpretation happens at run-time there's a performance overhead you can't get rid of. It's frustrating because all the information is there in the format string at compile time, but it's not evaluated until run-time. 3. A worse problem occurs because the evaluation of the format string doesn't happen until run-time: there can be no compile-time error checking. You're probably very familiar with this problem if you've tried to find bugs that came from using the wrong number or type of arguments in a printf( ) statement. C++ makes a big deal out of compile-time error checking to find errors early and make your life easier. 4. For C++, the most important problem is that the printf( ) family of functions is not particularly extensible. They're really designed to handle the four basic data types in C (char, int, float, double and their variations). For a language like C++, whose goal is to be able to easily add new data types, this is an ungainly restriction. It also has the much greater challenge that it can never know all the classes it must accommodate, but it must nevertheless be adaptable to use any new class. Thus its constraints required that this first class be a truly inspired design. This chapter won't look at the details of the design and how to add iostream functionality to your own classes (you'll learn that in a later chapter). First, you need to learn to use iostreams. Sneak preview of operator overloading Before you can use the iostreams library, you must understand one new feature of the language that won't be covered in detail until a later chapter. To use iostreams, you need to know that in C++ all the operators can take on different meanings. In Chapter 4, you learned how function overloading allows you to use the same function name with different argument lists. Now imagine that when the compiler sees an expression consisting of an argument followed by an operator followed by an argument, it simply calls a function. That is, an operator is simply a function call with a different syntax. Of course, this is C++, which is very particular about data types. So there must be a previously declared function to match that operator and those particular argument types, or the compiler will not accept the expression. What most people find immediately disturbing about operator overloading is the thought that maybe everything they know about operators in C is suddenly wrong. This is absolutely false. In particular, operators for built-in types won't suddenly start working differently - you cannot change their meaning. Overloaded operators can be created only where new data types are involved. Inserters and extractors In the iostreams library, two operators have been overloaded to make the use of iostreams easy. A stream is an object that formats and holds bytes. You can have an input stream (istream) or an output stream (ostream). The single interface you learn also works for extensions added to support new classes. If a stream is capable of producing bytes (an istream), you can get information from the stream using an extractor. The extractor produces and formats the type of information that's expected by the destination object. To see an example of this, you can use the cin object, which is the iostream equivalent of stdin in C, that is, redirectable standard input. This object is pre-defined whenever you include the IOSTREAM.H header file. Manipulators One new element has been added here: a manipulator called endl. A manipulator acts on the stream itself; in this case it inserts a newline and flushes the stream (puts out all pending characters that have been stored in the internal stream buffer but not yet output). These are all the manipulators in IOSTREAM.H, but there are more in IOMANIP.H you'll see later in the chapter. If the input produces an unexpected value, the process is skewed, and it's very difficult to recover. In addition, formatted input defaults to whitespace delimiters. It seems cin and the extractor are provided only for completeness, and this is probably a good way to look at it. In practice, you'll usually want to get your input a line at a time as a sequence of characters and then scan them and perform conversions once they're safely in a buffer. This way you don't have to worry about the input routine choking on unexpected data. Another thing to consider is the whole concept of a command-line interface. This has made sense in the past when the console was little more than a glass typewriter, but the world is rapidly changing to one where the graphical user interface (GUI) dominates. Iostreams for files still works fine with a GUI. 2. Read the input without attempting to convert it. Once you've got it someplace where it can't foul things up during conversion, then you can safely scan it. 3. Output is different. If you're using a GUI, cout doesn't work and you must send it to a file (which is identical to sending it to cout) or use the GUI facilities for data display. Otherwise it often makes sense to send it to cout. In both cases, the output formatting functions of iostreams are highly useful. Line-oriented input To grab input a line at a time, you have two choices: the member functions get( ) and getline( ). Both functions take three arguments: a pointer to a character buffer in which to store the result, the size of that buffer (so they don't overrun it), and the terminating character, to know when to stop reading input. The terminating character has a default value of '\n', which is what you'll usually use. Both functions store a zero in the result buffer when they encounter the terminating character in the input. So what's the difference? Subtle, but important: get( ) stops when it sees the delimiter in the input stream, but it doesn't extract it from the input stream. Thus, if you did another get( ) using the same delimiter it would immediately return with no fetched input. Generally, when you're processing a text file that you read a line at a time, you'll want to use getline( ). That is explored later in the chapter. Reading raw bytes If you know exactly what you're dealing with and want to move the bytes directly into a variable, array, or structure in memory, you can use read( ). The first argument is a pointer to the destination memory, and the second is the number of bytes to read. This is especially useful if you've previously stored the information to a file, for example, in binary form using the complementary write( ) member function for an output stream. You'll see examples of all these functions later. Error handling All the versions of get( ) and getline( ) return the input stream from which the characters came except for get( ) with no arguments, which returns the next character or EOF. If you get the input stream object back, you can ask it if it's still OK. In fact, you can ask any iostream object if it's OK using the member functions good( ), eof( ), fail( ), and bad( ). However, as mentioned earlier, the state of an input stream generally gets corrupted in weird ways only when you're trying to do input to specific types and the type read from the input is inconsistent with what is expected. Then of course you have the problem of what to do with the input stream to correct the problem. Fortunately, testing for this turns out to be simple and can be done inside of conditionals, such as while(cin) or if(cin). For now you'll have to accept that when you use an input stream object in this context, the right value is safely, correctly and magically produced to indicate whether the object has reached the end of the input. You can also use the Boolean NOT operator!, as in if(!cin), to indicate the stream is not OK; that is, you've probably reached the end of input and should quit trying to read the stream. There are times when the stream becomes not-OK, but you understand this condition and want to go on using it. For example, if you reach the end of an input file, the eofbit and failbit are set, so a conditional on that stream object will indicate the stream is no longer good. However, you may want to continue using the file, by seeking to an earlier position and reading more data. To correct the condition, simply call the clear( ) member function.54 File iostreams Manipulating files with iostreams is much easier and safer than using STDIO.H in C. All you do to open a file is create an object; the constructor does the work. You don't have to explicitly close a file (although you can, using the close( ) member function) because the destructor will close it when the object goes out of scope. To create a file that defaults to input, make an ifstream object. To create one that defaults to output, make an ofstream object. Here's an example that shows many of the features discussed so far. Here again the object, used in a situation where the compiler expects an integral result, produces a value that indicates success or failure. These are discussed in Chapter 10.) The first while loop demonstrates the use of two forms of the get( ) function. The first gets characters into a buffer and puts a zero terminator in the buffer when either SZ - 1 characters have been read or the third argument (defaulted to '\n') is encountered. You can also use the ignore( ) member function, which has two defaulted arguments. The first is the number of characters to throw away, and defaults to one. The second is the character at which the ignore( ) function quits (after extracting it) and defaults to EOF. Next you see two output statements that look very similar: one to cout and one to the file out. Notice the convenience here; you don't need to worry about what kind of object you're dealing with because the formatting statements work the same with all ostream objects. The first one echoes the line to standard output, and the second writes the line out to the new file and includes a line number. To demonstrate getline( ), it's interesting to open the file we just created and strip off the line numbers. To ensure the file is properly closed before opening it to read, you have two choices. You can surround the first part of the program in braces to force the out object out of scope, thus calling the destructor and closing the file, which is done here. You can also call close( ) for both files; if you want, you can even reuse the in object by calling the open( ) member function (you can also create and destroy the object dynamically on the heap as is in Chapter 11). The second while loop shows how getline( ) removes the terminator character (its third argument, which defaults to '\n') from the input stream when it's encountered. Although getline( ), like get( ), puts a zero in the buffer, it still doesn't insert the terminating character. Open modes You can control the way a file is opened by changing a default argument. The following table shows the flags that control the mode of the file: Flag Function ios::in Opens an input file. Use this as an open mode for an ofstream to prevent truncating an existing file. When used for an ofstream without ios::app, ios::ate or ios::in, ios::trunc is implied. Default is text mode. These flags can be combined using a bitwise OR. Iostream buffering Whenever you create a new class, you should endeavor to hide the details of the underlying implementation as possible from the user of the class. Try to show them only what they need to know and make the rest private to avoid confusion. There comes a time, however, when it becomes important to be able to send messages to the part of the iostream that produces and consumes bytes. To provide this part with a common interface and still hide its underlying implementation, it is abstracted into its own class, called streambuf. Each iostream object contains a pointer to some kind of streambuf. This is accomplished, of course, by calling member functions for the streambuf object. Currently, the most important thing for you to know is that every iostream object contains a pointer to a streambuf object, and the streambuf has some member functions you can call if you need to. To allow you to access the streambuf, every iostream object has a member function called rdbuf( ) that returns the pointer to the object's streambuf. This way you can call any member function for the underlying streambuf. It's a much more elegant approach. The open will fail if the file doesn't exist, and this failure is caught by the assert(in). This is not only more succinct to code, it is often more efficient than moving the bytes one at a time. Using get( ) with a streambuf There is a form of get( ) that allows you to write directly into the streambuf of another object. The first argument is the destination streambuf (whose address is mysteriously taken using a reference, discussed in Chapter 9), and the second is the terminating character, which stops the get( ) function. The get( ) function, remember, doesn't pull the terminating character from the input stream, so it must be removed using ignore( ) so get( ) doesn't just bonk up against the newline forever (which it will, otherwise). You probably won't need to use a technique like this very often, but it may be useful to know it exists. Seeking in iostreams Each type of iostream has a concept of where its next character will come from (if it's an istream) or go (if it's an ostream). In some situations you may want to move this stream position. The streampos approach requires that you first call a tell function: tellp( ) for an ostream or tellg( ) for an istream. The second approach is a relative seek and uses overloaded versions of seekp( ) and seekg( ). The first argument is the number of bytes to move: it may be positive or negative. Because this is a type of istream, seekg( ) is used to position the get pointer. The first call seeks zero bytes off the end of the file, that is, to the end. Because a streampos is a typedef for a long, calling tellg( ) at that point also returns the size of the file, which is printed out. If you try to seek positively from the end of the file, the get pointer will just stay at the end. The streampos at that point is captured into sp2, then a seekg( ) is performed back to the beginning of the file so the whole thing can be printed out using the streambuf pointer produced with rdbuf( ). Finally, the overloaded version of seekg( ) is used with the streampos sp2 to move to the previous position, and the last portion of the file is printed out. The following code first creates an ifstream with flags that say it's both an input and an output file. This gives us a safe text file to play around with. Then the aforementioned technique is used to create two objects that read and write to the same file. The put pointer, however, is set to the end of the file because Where does this end up? appears appended to the file. However, if the put pointer is moved to the beginning with a seekp( ), all the inserted text overwrites the existing text. Both writes are seen when the get pointer is moved back to the beginning with a seekg( ), and the file is printed out. Of course, the file is automatically saved and closed when out2 goes out of scope and its destructor is called. Although these are not an official part of Standard C++, they have been around a long time so compilers will no doubt leave in the strstream support in perpetuity, to compile legacy code. You should always use stringstreams, but it's certainly likely that you'll come across code that uses strstreams and at that point this section should come in handy. In addtion, this section should make it fairly clear why stringstreams have replace strstreams. A strstream works directly with memory instead of a file or standard output. It allows you to use the same reading and formatting functions to manipulate bytes in memory. On old computers the memory was referred to as core so this type of functionality is often called in-core formatting. The class names for strstreams echo those for file streams. If you want to create a strstream to extract characters from, you create an istrstream. If you want to put characters into a strstream, you create an ostrstream. String streams work with memory, so you must deal with the issue of where the memory comes from and where it goes. This isn't terribly complicated, but you must understand it and pay attention (it turned out is was too easy to lose track of this particular issue, thus the birth of stringstreams). User-allocated storage The easiest approach to understand is when the user is responsible for allocating the storage. With istrstreams this is the only allowed approach. There are two constructors: istrstream::istrstream(char* buf); istrstream::istrstream(char* buf, int size); The first constructor takes a pointer to a zero-terminated character array; you can extract bytes until the zero. The second constructor additionally requires the size of the array, which doesn't have to be zero-terminated. When you hand an istrstream constructor the address of an array, that array must already be filled with the characters you want to extract and presumably format into some other data type. The compiler handles the static storage allocation of the string in istrstream s(47 1.414 This is a test); You can also hand it a pointer to a zero-terminated string allocated on the stack or the heap. This isn't the first whitespace-delimited set of characters because it depends on the data type it's being extracted into. For example, if the string were instead, 1.414 47 This is a test, then i would get the value one because the input routine would stop at the decimal point. Then f would get 0.414. This could be useful if you want to break a floating-point number into a whole number and a fraction part. Otherwise it would seem to be an error. As you may already have guessed, buf2 doesn't get the rest of the string, just the next whitespace-delimited word. In general, it seems the best place to use the extractor in iostreams is when you know the exact sequence of data in the input stream and you're converting to some type other than a character string. However, if you want to extract the rest of the string all at once and send it to another iostream, you can use rdbuf( ) as shown. Output strstreams Output strstreams also allow you to provide your own storage; in this case it's the place in memory the bytes are formatted into. The appropriate constructor is ostrstream::ostrstream(char*, int, int = ios::out); The first argument is the preallocated buffer where the characters will end up, the second is the size of the buffer, and the third is the mode. If the mode is left as the default, characters are formatted into the starting address of the buffer. If the mode is either ios::ate or ios::app (same effect), the character buffer is assumed to already contain a zero-terminated string, and any new characters are added starting at the zero terminator. The second constructor argument is the size of the array and is used by the object to ensure it doesn't overwrite the end of the array. If you fill the array up and try to add more bytes, they won't go in. An important thing to remember about ostrstreams is that the zero terminator you normally need at the end of a character array is not inserted for you. When you're ready to zero-terminate the string, use the special manipulator ends. Once you've created an ostrstream you can insert anything you want, and it will magically end up formatted in the memory buffer. The approach shown, using getline( ), gets the input until the user presses the carriage return. This input is fetched into buf, which is subsequently used to construct the ostrstream os. If the third argument ios::app weren't supplied, the constructor would default to writing at the beginning of buf, overwriting the line that was just collected. However, the append flag causes it to put the rest of the formatted information at the end of the string. You can see that, like the other output streams, you can use the ordinary formatting tools for sending bytes to the ostrstream. The only difference is that you're responsible for inserting the zero at the end with ends. Note that endl inserts a newline in the strstream, but no zero. However, it's also possible to send the information out with os.rdbuf( ). When you do this, the get pointer inside the streambuf is moved forward as the characters are output. Automatic storage allocation Output strstreams (but not istrstreams) give you a second option for memory allocation: they can do it themselves. All you do is create an ostrstream with no constructor arguments: ostrstream A; Now A takes care of all its own storage allocation on the heap. You can put as many bytes into A as you want, and if it runs out of storage, it will allocate more, moving the block of memory, if necessary. This is a very nice solution if you don't know how much space you'll need, because it's completely flexible. But what happens if you want the physical address of the memory that A's characters have been formatted into? It's readily available - you simply call the str( ) member function: char* cp = A.str(); There's a problem now. What if you want to put more characters into A? It would be OK if you knew A had already allocated enough storage for all the characters you want to give it, but that's not true. Generally, A will run out of storage when you give it more characters, and ordinarily it would try to allocate more storage on the heap. This would usually require moving the block of memory. But the stream objects has just handed you the address of its memory block, so it can't very well move that block, because you're expecting it to be at a particular location. The way an ostrstream handles this problem is by freezing itself. As long as you don't use str( ) to ask for the internal char*, you can add as many characters as you want to the ostrstream. It will allocate all the necessary storage from the heap, and when the object goes out of scope, that heap storage is automatically released. However, if you call str( ), the ostrstream becomes frozen. You can't add any more characters to it. Rather, you aren't supposed to - implementations are not required to detect the error. Adding characters to a frozen ostrstream results in undefined behavior. In addition, the ostrstream is no longer responsible for cleaning up the storage. You took over that responsibility when you asked for the char* with str( ). To prevent a memory leak, the storage must be cleaned up somehow. There are two approaches. The more common one is to directly release the memory when you're done. To understand this, you need a sneak preview of two new keywords in C++: new and delete. As you'll see in Chapter 11, these do quite a bit, but for now you can think of them as replacements for malloc( ) and free( ) in C. The operator new returns a chunk of memory, and delete frees it. It's important to know about them here because virtually all memory allocation in C++ is performed with new, and this is also true with ostrstream. You do this by calling freeze( ), which is a member function of the ostrstream's streambuf. In addition, you can add more bytes to A. However, this may cause the storage to move, so you better not use any pointer you previously got by calling str( ) - it won't be reliable after adding more characters. At that point, s is frozen. We want to add more characters to s, but for it to have any effect, the put pointer must be backed up one so the next character is placed on top of the zero inserted by ends. Then s is unfrozen by fetching the underlying streambuf pointer using rdbuf( ) and calling freeze(0). At this point s is like it was before calling str( ): We can add more characters, and cleanup will occur automatically, with the destructor. It is possible to unfreeze an ostrstream and continue adding characters, but it is not common practice. Normally, if you want to add more characters once you've gotten the char* of a ostrstream, you create a new one, pour the old stream into the new one using rdbuf( ) and continue adding new characters to the new ostrstream. After printing out the old and new char* values, the storage is explicitly released with delete because the second call to str( ) froze the string again. It's interesting to note that if you don't insert a string to s before calling str( ), the result is zero. This means no storage is allocated until the first time you try to insert bytes to the ostrstream. A better way Again, remember that this section was only left in to support legacy code. You should always use string and stringstream rather than character arrays and strstream. The former is much safer and easier to use and will help ensure your projects get finished faster. Output stream formatting The whole goal of this effort, and all these different types of iostreams, is to allow you to easily move and translate bytes from one place to another. It certainly wouldn't be very useful if you couldn't do all the formatting with the printf( ) family of functions. In this section, you'll learn all the output formatting functions that are available for iostreams, so you can get your bytes the way you want them. The formatting functions in iostreams can be somewhat confusing at first because there's often more than one way to control the formatting: through both member functions and manipulators. On the other hand, there are specific member functions to set and read values for the fill character, the field width, and the precision. In an attempt to clarify all this, the internal formatting data of an iostream is examined first, along with the member functions that can modify that data. Internal formatting data The class ios (which you can see in the header file IOSTREAM.H) contains data members to store all the formatting data pertaining to that stream. Some of this data has a range of values and is stored in variables: the floating-point precision, the output field width, and the character used to pad the output (normally a space). The rest of the formatting is determined by flags, which are usually combined to save space and are referred to collectively as the format flags. You can find out the value of the format flags with the ios::flags( ) member function, which takes no arguments and returns a long (typedefed to fmtflags) that contains the current format flags. All the rest of the functions make changes to the format flags and return the previous value of the format flags. More often, you change one flag at a time using the remaining three functions. The use of setf( ) can seem more confusing: To know which overloaded version to use, you must know what type of flag you're changing. There are two types of flags: ones that are simply on or off, and ones that work in a group with other flags. The format used can be read by the C++ compiler. For example, to show the plus sign for cout, you say cout.setf(ios::showpos). To stop showing the plus sign, you say cout.unsetf(ios::showpos). The last two flags deserve some explanation. You turn on unit buffering when you want to make sure each character is output as soon as it is inserted into an output stream. You could also use unbuffered output, but unit buffering provides better performance. If you discover your iostream output and printf( ) output are occurring in the wrong order, try setting this flag. Format fields The second type of formatting flags work in a group. You can have only one of these flags on at a time, like the buttons on old car radios - you push one in, the rest pop out. Unfortunately this doesn't happen automatically, and you have to pay attention to what flags you're setting so you don't accidentally call the wrong setf( ) function. For example, there's a flag for each of the number bases: hexadecimal, decimal, and octal. Collectively, these flags are referred to as the ios::basefield. If the ios::dec flag is set and you call setf(ios::hex), you'll set the ios::hex flag, but you won't clear the ios::dec bit, resulting in undefined behavior. The proper thing to do is call the second form of setf( ) like this: setf(ios::hex, ios::basefield). This function first clears all the bits in the ios::basefield, then sets ios::hex. Thus, this form of setf( ) ensures that the other flags in the group pop out whenever you set one. Of course, the hex( ) manipulator does all this for you, automatically, so you don't have to concern yourself with the internal details of the implementation of this class or to even care that it's a set of binary flags. Later you'll see there are manipulators to provide equivalent functionality in all the places you would use setf( ). Here are the flag groups and their effects: ios::basefield effect ios::dec Format integral values in base 10 (decimal) (default radix). Precision field indicates number of digits after the decimal point. Precision field indicates number of digits after the decimal point. Pad on the left with the fill character. This is the default alignment. See ios::floatfield table for the meaning of precision. The fill and precision values are fairly straightforward, but width requires some explanation. When the width is zero, inserting a value will produce the minimum number of characters necessary to represent that value. A positive width means that inserting a value will produce at least as many characters as the width; if the value has less than width characters, the fill character is used to pad the field. However, the value will never be truncated, so if you try to print 123 with a width of two, you'll still get 123. The field width specifies a minimum number of characters; there's no way to specify a maximum number. The width is also distinctly different because it's reset to zero by each inserter or extractor that could be influenced by its value. It's really not a state variable, but an implicit argument to the inserters and extractors. If you want to have a constant width, you have to call width( ) after each insertion or extraction. The macro D(a) uses the preprocessor stringizing to turn a into a string to print out. Then it reiterates a so the statement takes effect. The macro sends all the information out to a file called T, which is the trace file. T.width(40); 0000000000000000000000Is there any more? Formatting manipulators As you can see from the previous example, calling the member functions can get a bit tedious. To make things easier to read and write, a set of manipulators is supplied to duplicate the actions provided by the member functions. IOSTREAM.H56 also includes ws, endl, ends, and flush and the additional set shown here: manipulator effect showbase noshowbase Indicate the numeric base (dec, oct, or hex) when printing an integral value. The format used can be read by the C++ compiler. Right-align, pad on left. Fill between leading sign or base indicator and value. Manipulators with arguments If you are using manipulators with arguments, you must also include the header file IOMANIP.H. This contains code to solve the general problem of creating manipulators with arguments. In addition, it has six predefined manipulators: manipulator effect setiosflags (fmtflags n) Sets only the format flags specified by n. Setting remains in effect until the next change, like ios::setf( ). Setting remains in effect until the next change, like ios::unsetf( ). You might as well use dec, oct, and hex for output. If you're using a lot of inserters, you can see how this can clean things up. As an example, here's the previous program rewritten to use the manipulators. Note the calls to setiosflags( ) and resetiosflags( ), where the flags have been bitwise-ORed together. This could also have been done with setf( ) and unsetf( ) in the previous example. Creating manipulators (Note: This section contains some material that will not be introduced until later chapters.) Sometimes you'd like to create your own manipulators, and it turns out to be remarkably simple. A zero-argument manipulator like endl is simply a function that takes as its argument an ostream reference (references are a different way to pass arguments, discussed in Chapter 9). So the compiler says is there a function I can call that takes the address of a function as its argument? There is a pre-defined function in IOSTREAM.H to do this; it's called an applicator. The applicator calls the function, passing it the ostream object as an argument. You don't need to know how the applicator works to create your own manipulator; you only need to know the applicator exists. Effectors As you've seen, zero-argument manipulators are quite easy to create. But what if you want to create a manipulator that takes arguments? The iostream library has a rather convoluted and confusing way to do this, but Jerry Schwarz, the creator of the iostream library, suggests58 a scheme he calls effectors. Here's an example with two effectors. The Bin effector relies on the fact that shifting an unsigned number to the right shifts zeros into the high bits. ULONG_MAX (the largest unsigned long value, from the standard include file LIMITS.H) is used to produce a value with the high bit set, and this value is moved across the number in question (by shifting it), masking each bit. Initially the problem with this technique was that once you created a class called Fixw for char* or Bin for unsigned long, no one else could create a different Fixw or Bin class for their type. However, with namespaces (covered in Chapter XX), this problem is eliminated. Iostream examples In this section you'll see some examples of what you can do with all the information you've learned in this chapter. Although many tools exist to manipulate bytes (stream editors like sed and awk from Unix are perhaps the most well known, but a text editor also fits this category), they generally have some limitations. The programs you write with iostreams have none of these limitations: They're fast, portable, and flexible. It's a very useful tool to have in your kit. Code generation The first examples concern the generation of programs that, coincidentally, fit the format used in this book. This provides a little extra speed and consistency when developing code. This returns an int so it must be explicitly cast to a char. This name is used in the headline, followed by the remainder of the generated file. Maintaining class library source The second example performs a more complex and useful task. Generally, when you create a class you think in library terms, and make a header file NAME.H for the class declaration and a file where the member functions are implemented, called NAME.CPP. They could be different, so it throws up its hands and gives an error message.) This example allows you to create a new header-implementation pair of files, or to modify an existing pair. If the files already exist, it checks and potentially modifies the files, but if they don't exist, it creates them using the proper format. Rather than creating a lot of individually named buffers and ostrstream objects, a single set of names is created in the enum bufs. Then two arrays are created: an array of character buffers and an array of ostrstream objects built from those character buffers. Note that in the definition for the two-dimensional array of char buffers b, the number of char arrays is determined by bufnum, the last enumerator in bufs. When you create an enumeration, the compiler assigns integral values to all the enum labels starting at zero, so the sole purpose of bufnum is to be a counter for the number of enumerators in buf. The length of each string in b is SZ. Of course, this is the form of the ostrstream constructor that takes two arguments (the buffer address and buffer size), so the constructor calls must be formed accordingly inside the aggregate initializer list. Using the bufs enumerators, the appropriate array element of b is tied to the corresponding osarray object. Once the array is created, the objects in the array can be selected using the enumerators, and the effect is to fill the corresponding b element. You can see how each string is built in the lines following the ostrstream array definition. Once the strings have been created, the program attempts to open existing versions of both the header and CPP file as ifstreams. If you test the object using the operator '!' and the file doesn't exist, the test will fail. If the header or implementation file doesn't exist, it is created using the appropriate lines of text built earlier. If the files do exist, then they are verified to ensure the proper format is followed. This is accomplished with the Standard C library function strstr( ). If the first line doesn't conform, the one created earlier is inserted into an ostrstream that has been created to hold the edited file. In the header file, the whole file is searched (again using strstr( )) to ensure it contains the three guard lines; if not, they are inserted. The implementation file is checked for the existence of the line that includes the header file (although the compiler effectively guarantees its existence). In both cases, the original file (in its strstream) and the edited file (in the ostrstream) are compared to see if there are any changes. If there are, the existing file is closed, and a new ofstream object is created to overwrite it. The ostrstream is output to the file after a special change marker is added at the beginning, so you can use a text search program to rapidly find any files that need reviewing to make additional changes. Detecting compiler errors All the code in this book is designed to compile as shown without errors. Each file is read a line at a time, and each line is searched for the marker appearing at the head of the line; the line is modified and put into the error line list and into the strstream edited. When the whole file is processed, it is closed (by reaching the end of a scope), reopened as an output file and edited is poured into the file. Also notice the counter is saved in an external file, so the next time this program is invoked it continues to sequence the counter. A simple datalogger This example shows an approach you might take to log data to disk and later retrieve it for processing. The example is meant to produce a temperature-depth profile of the ocean at various points. The print( ) function formats the DataPoint in a readable form onto an ostream object (the argument to print( )). The default is to right-justify the data within the field. The time information consists of two digits each for the hours, minutes and seconds, so the width is set to two with setw( ) in each case. The latitude and longitude are zero-terminated character fields, which hold the information as degrees (here, '*' denotes degrees), minutes ('), and seconds(). You can certainly devise a more efficient storage layout for latitude and longitude if you desire. Generating test data Here's a program that creates a file of test data in binary form (using write( )) and a second file in ASCII form using DataPoint::print( ). You can also print it out to the screen but it's easier to inspect in file form. The Standard C library function time( ), when called with a zero argument, returns the current time as a time_t value, which is the number of seconds elapsed since 00:00:00 GMT, January 1 1970 (the dawning of the age of Aquarius?). The current time is the most convenient way to seed the random number generator with the Standard C library function srand( ), as is done here. This tm, however, is a static structure inside the localtime( ) function, which is rewritten every time localtime( ) is called. To copy the contents into the tm struct inside DataPoint, you might think you must copy each element individually. However, all you must do is a structure assignment, and the compiler will take care of the rest. This means the right-hand side must be a structure, not a pointer, so the result of localtime( ) is dereferenced. The desired result is achieved with d.Time(*localtime(andtimer)); After this, the timer is incremented by 55 seconds to give an interesting interval between readings. The latitude and longitude used are fixed values to indicate a set of readings at a single location. Both the depth and the temperature are generated with the Standard C library rand( ) function, which returns a pseudorandom number between zero and the constant RAND_MAX. To put this in a desired range, use the modulus operator % and the upper end of the range. These numbers are integral; to add a fractional part, a second call to rand( ) is made, and the value is inverted after adding one (to prevent divide-by-zero errors). In effect, the DATA.BIN file is being used as a container for the data in the program, even though the container exists on disk and not in RAM. To send the data out to the disk in binary form, write( ) is used. The first argument is the starting address of the source block - notice it must be cast to an unsigned char* because that's what the function expects. The second argument is the number of bytes to write, which is the size of the DataPoint object. Because no pointers are contained in DataPoint, there is no problem in writing the object to disk. If the object is more sophisticated, you must implement a scheme for serialization. In the following program, you can see how simple this data recovery is. After the test file is created, the records are read at the command of the user. The read( ) statement reads a single record and places it directly into the DataPoint d. At this point, however, you can't move the get pointer back and read more records because the state of the stream won't allow further reads. So the clear( ) function is called to reset the failbit. Once the record is read in from disk, you can do anything you want with it, such as perform calculations or make graphs. Here, it is displayed to further exercise your knowledge of iostream formatting. The rest of the program displays a record number (represented by recnum) selected by the user. As before, the precision is fixed at four decimal places, but this time everything is left justified. Then, because the failbit was set, it must be reset with a call to clear( ) so the next read( ) is successful (assuming it's in the right range). Of course, you can also open the binary data file for writing as well as reading. This way you can retrieve the records, modify them, and write them back to the same location, thus creating a flat-file database management system. In my very first programming job, I also had to create a flat-file DBMS - but using BASIC on an Apple II. It took months, while this took minutes. Of course, it might make more sense to use a packaged DBMS now, but with C++ and iostreams you can still do all the low-level operations that are necessary in a lab. Counting editor Often you've got some editing task where you must go through and sequentially number something, but all the other text is duplicated. I encountered this problem when pasting digital photos into a Web page - I got the formatting just right, then duplicated it, then had the problem of incrementing the photo number for each one. So I replaced the photo number with XXX, duplicated that, and wrote the following program to find and replace the XXX with an incremented count. It then pieces it out in chunks to the smaller files, generating the names as it goes. Of course, you can come up with a possibly more reasonable strategy that reads a chunk, creates a file, reads another chunk, etc. Note that this program can be run on the server, so you only have to download the big file once and then break it up once it's on the server. In all likelihood, it is all you need to create programs using iostreams. Exercises 1. Open a file by creating an ifstream object called in. Make an ostrstream object called os, and read the entire contents into the ostrstream using the rdbuf( ) member function. Get the address of os's char* with the str( ) function, and capitalize every character in the file using the Standard C toupper( ) macro. Write the result out to a new file, and delete the memory allocated by os. 2. Create a program that opens a file (the first argument on the command line) and searches it for any one of a set of words (the remaining arguments on the command line). Read the input a line at a time, and print out the lines (with line numbers) that match. 3. Write a program that adds a copyright notice to the beginning of all source-code files. This is a small modification to exercise 1. 4. Use your favorite text-searching program (grep, for example) to output the names (only) of all the files that contain a particular pattern. Redirect the output into a file. Write a program that uses the contents of that file to generate a batch file that invokes your editor on each of the files found by the search program. XX: Advanced templates The typename keyword template-templates Controlling template instantiation The export keyword 20: STL Containers and Iterators Container classes are the solution to a specific kind of code reuse problem. They are building blocks used to create object-oriented programs - they make the internals of a program much easier to construct. A container class describes an object that holds other objects. Container classes are so important that they were considered fundamental to early object-oriented languages. In Smalltalk, for example, programmers think of the language as the program translator together with the class library, and a critical part of that library is the container classes. So it became natural that C++ compiler vendors also include a container class library. You'll note that the vector was so useful that it was introduced in its simplest form very early in this book. Like many other early C++ libraries, early container class libraries followed Smalltalk's object-based hierarchy, which worked well for Smalltalk, but turned out to be awkward and difficult to use in C++. Another approach was required. This chapter attempts to slowly work you into the concepts of the STL, which is a powerful library of containers (as well as algorithms, but these are covered in the following chapter). In the past, I have taught that there is a relatively small subset of elements and ideas that you need to understand in order to get much of the usefulness from the STL. Although this can be true it turns out that understanding the STL more deeply is important to gain the full power of the library. This chapter and the next probe into the STL containers and algorithms. STL reference documentation You will notice that this chapter does not contain exhaustive documentation describing each of the member functions in each STL container. When you're actively programming, these two sources should adequately satisfy your reference needs (and you can use them to look up anything in this chapter that isn't clear to you). In addition, the STL books listed in Appendix XX will provide you with other resources. The Standard Template Library The C++ STL59 is a powerful library intended to satisfy the vast bulk of your needs for containers and algorithms, but in a completely portable fashion. Thus, it will benefit you greatly to look first to the STL for containers and algorithms, before looking at vendor-specific solutions. A fundamental principle of software design is that all problems can be simplified by introducing an extra level of indirection. This simplicity is achieved in the STL using iterators to perform operations on a data structure while knowing as little as possible about that structure, thus producing data structure independence. With the STL, this means that any operation that can be performed on an array of objects can also be performed on an STL container of objects and vice versa. The STL containers work just as easily with built-in types as they do with user-defined types. If you learn the library, it will work on everything. The drawback to this independence is that you'll have to take a little time at first getting used to the way things are done in the STL. However, the STL uses a consistent pattern, so once you fit your mind around it, it doesn't change from one STL tool to another. Consider an example using the STL set class. A set will allow only one of each object value to be inserted into itself. Very often the activities involved in using a set are simply insertion and a test to see whether it contains the element. You can also form a union, intersection, or difference of sets, and test to see if one set is a subset of another. In this example, the values 1 - 10 are inserted into the set 25 times, and the results are printed out to show that only one of each of the values is actually retained in the set. The copy( ) function is actually the instantiation of an STL template function, of which there are many. These template functions are generally referred to as the STL Algorithms and will be the subject of the following chapter. However, several of the algorithms are so useful that they will be introduced in this chapter. Here, copy( ) shows the use of iterators. The set member functions begin( ) and end( ) produce iterators as their return values. This places int objects on cout and separates them with a newline. Because of its genericity, copy( ) is certainly not restricted to printing on a stream. It can be used in virtually any situation: it needs only three iterators to talk to. All of the algorithms follow the form of copy( ) and simply manipulate iterators (that's the extra indirection). Now consider taking the form of Intset.cpp and reshaping it to display a list of the words used in a document. The solution becomes remarkably simple. The words are pulled from a file, but everything else is the same as in Intset.cpp. So it approximately breaks an input stream up into words. Each string is placed in the words vector using push_back( ), and the copy( ) function is used to display the results. Because of the way set is implemented (as a tree), the words are automatically sorted. Consider how much effort it would be to accomplish the same task in C, or even in C++ without the STL. The basic concepts The primary idea in the STL is the container (also known as a collection), which is just what it sounds like: a place to hold things. You need containers because objects are constantly marching in and out of your program and there must be someplace to put them while they're around. You can't make named local objects because in a typical program you don't know how many, or what type, or the lifetime of the objects you're working with. So you need a container that will expand whenever necessary to fill your needs. All the containers in the STL hold objects and expand themselves. In addition, they hold your objects in a particular way. The difference between one container and another is the way the objects are held and how the sequence is created. Let's start by looking at the simplest containers. A vector is a linear sequence that allows rapid random access to its elements. However, it's expensive to insert an element in the middle of the sequence, and is also expensive when it allocactes additional storage. A list the third type of basic linear sequence, but it's expensive to move around randomly and cheap to insert an element in the middle. Thus list, deque and vector are very similar in their basic functionality, but different in the cost of their activities. So for your first shot at a program, you could use any one, and only experiment with the others if you're tuning for efficiency. Many of the problems you set out to solve will only require a simple linear sequence like a vector, deque or list. All three have a member function push_back( ) which you use to insert a new element at the back of the sequence (deque and list also have push_front( )). But now how do you retrieve those elements? Since it would be nicest to learn a single interface, we'll use the one defined for all STL containers: the iterator. An iterator is a class that abstracts the process of moving through a sequence. It allows you to select each element of a sequence without knowing the underlying structure of that sequence. This is a powerful feature, partly because it allows us to learn a single interface that works with all containers, and partly because it allows containers to be used interchangeably. One more observation and you're ready for another example. Even though the STL containers hold objects by value (that is, they hold the whole object inside themselves) that's probably not the way you'll generally use them time if you're doing object-oriented programming. That's because in OOP, most of the time you'll create objects on the heap with new and then upcast the address to the base-class type, later manipulating it as a pointer to the base class. The beauty of this is that you don't worry about the specific type of object you're dealing with, which greatly reduces the complexity of your code and increases the maintainability of your program. This process of upcasting is what you try to do in OOP, so you'll usually be using containers of pointers. Consider the classic shape example where shapes have a set of common operations, and you have different types of shapes. Shape is a pure abstract base class (because of the pure specifier =0) that defines the interface for all types of shapes; the derived classes redefine the virtual function draw( ) to perform the appropriate operation. Now we'd like to create a bunch of different types of Shape object, but where to put them? In an STL container, of course. Notice that the container type name must be used to produce the appropriate iterator, which is defined as a nested class. That's what you'll want to do 90% of the time. The first for loop creates an iterator and sets it to the beginning of the sequence by calling the begin( ) member function for the container. All containers have begin( ) and end( ) member functions that produce an iterator selecting, respectively, the beginning of the sequence and one past the end of the sequence. To test to see if you're done, you make sure you're!= to the iterator produced by end( ). You dereference it using (what else) the '*' (which is actually an overloaded operator). What you get back is whatever the container is holding. This container holds Shape*, so that's what *i produces. The parentheses are ugly but necessary to produce the proper order of evaluation. If you create an object on the heap with new and place its pointer in a container, the container can't tell if that pointer is also placed inside another container. So the STL just doesn't do anything about it, and puts the responsibility squarely in your lap. The last lines in the program move through and delete every object in the container so proper cleanup occurs. It's very interesting to note that you can change the type of container that this program uses with two lines. Everything else goes untouched. Now I can easily switch between vector and list and see which one works fastest for my needs. Especially because string manipulation is so common, character arrays are a great source of misunderstandings and bugs. Despite this, creating string classes remained a common exercise for beginning C++ programmers for many years. The Standard C++ library string class solves the problem of character array manipulation once and for all, keeping track of memory even during assignments and copy-constructions. You simply don't need to think about it (strings are thoroughly covered in Chapter XX). One of the places where this is particularly useful is pointed out in the prior example. At the end of main( ), it was necessary to move through the whole list and delete all the Shape pointers. It's as if the assumption of the STL designers was that containers of pointers weren't an interesting problem, although I assert that it is one of the more common things you'll want to do. Automatically deleting a pointer turns out to be a rather aggressive thing to do because of the multiple membership problem. If a container holds a pointer to an object, it's not unlikely that pointer could also be in another container. A pointer to an Aluminum object in a list of Trash pointers could also reside in a list of Aluminum pointers. Then which list is responsible for cleaning up that object - which list owns the object? This question is virtually eliminated if the object rather than a pointer resides in the list. Then it seems clear that when the list is destroyed, the objects it contains must also be destroyed. Here, the STL shines, as you can see when creating a container of string objects. A stringstream provides easy conversion from an int to a string of characters representing that int. Assembling string objects is quite easy, since operator+ is overloaded. First, no cleanup is necessary. Even if you were to put addresses of the string objects as pointers into other containers, it's clear that strings is the master list and maintains ownership of the objects. Second, you are effectively using dynamic object creation, and yet you never use new or delete! That's because, somehow, it's all taken care of for you by the vector (this is non-trivial. You can try to figure it out by looking at the header files for the STL - all the code is there - but it's quite an exercise). Thus your coding is significantly cleaned up. The limitation of holding objects instead of pointers inside containers is quite severe: you can't upcast from derived types, thus you can't use polymorphism. The problem with upcasting objects by value is that they get sliced and converted until their type is completely changed into the base type, and there's no remnant of the derived type left. It's pretty safe to say that you never want to do this. Inheriting from STL containers The power of instantly creating a sequence of elements is amazing, and it makes you realize how much time you've spent (or rather, wasted) in the past solving this particular problem. For example, many utility programs involve reading a file into memory, modifying the file and writing it back out to disk. One might as well take the functionality in Strvector.cpp and package it into a class for later reuse. Now the question is: do you create a member object of type vector, or do you inherit? So the list of strings should also be a vector, thus inheritance is desired. The constructor opens the file and reads it into the FileEditor, and write( ) puts the vector of string onto any ostream. Notice in write( ) that you can have a default argument for a reference. Often this is the way classes evolve - you start by creating a program to solve a particular application, then discover some commonly-used functionality within the program that can be turned into a class. A plethora of iterators As mentioned earlier, the iterator is the abstraction that allows a piece of code to be generic, and to work with different types of containers without knowing the underlying structure of those containers. Every container produces iterators. You must always be able to say: ContainerType::iterator ContainerType::const_iterator to produce the types of the iterators produced by that container. Every container has begin( ) method that produces an iterator indicating the beginning of the elements in the container, and an end( ) method that produces an iterator which is the as the past-the-end value of the container. If the container is const¸ begin( ) and end( ) produce const iterators. Every iterator can be moved forward to the next element using the operator++ (an iterator may be able to do more than this, as you shall see, but it must at least support forward movement with operator++). The basic iterator is only guaranteed to be able to perform == and!= comparisons. An iterator can be used to produce the element that it is currently selecting within a container by dereferencing the iterator. This can take two forms. Much of the time, this is all you need to know about iterators - that they are produced by begin( ) and end( ), and that you can use them to move through a container and select elements. Many of the problems that you solve, and the STL algorithms (covered in the next chapter) will allow you to just flail away with the basics of iterators. However, things can at times become more subtle, and in those cases you need to know more about iterators. The rest of this section gives you the details. Iterators in reversible containers All containers must produce the basic iterator. A container may also be reversible, which means that it can produce iterators that move backwards from the end, as well as the iterators that move forward from the beginning. A reversible container has the methods rbegin( ) (to produce a reverse_iterator selecting the end) and rend( ) (to produce a reverse_iterator indicating one past the beginning). If the container is const then rbegin( ) and rend( ) will produce const_reverse_iterators. All the basic sequence containers vector, deque and list are reversible containers. The associative containers set, multiset, map and multimap are also reversible. Using iterators with associative containers is a bit different, however, and will be delayed until those containers are more fully introduced. Iterator categories The iterators are classified into different categories which describe what they are capable of doing. They are generally described in order from the the categories with most restricted behavior to those with the most powerful behavior. Input: read-only, one pass The only predefined implementations of input iterators are istream_iterator and istreambuf_iterator, to read from an istream. As you can imagine, an input iterator can only be dereferenced once for each element that's selected, just as you can only read a particular portion of an input stream once. They can only move forward. There is a special constructor to define the past-the-end value. In summary, you can dereference it for reading (once only for each value), and move it forward. Output: write-only, one pass The complement of an input iterator, but for writing rather than reading. The only predefined implementations of output iterators are ostream_iterator and ostreambuf_iterator, to write to an ostream, and the less-commonly-used raw_storage_iterator. Again, these can only be dereferenced once for each written value, and they can only move forward. There is no concept of a terminal past-the-end value for an output iterator. Summarizing, you can dereference it for writing (once only for each value) and move it forward. As the name implies, you can only move forward. There are no predefined iterators that are only forward iterators. Is this really important? Why do you care about this categorization? When you're just using containers in a straightforward way (for example, just hand-coding all the operations you want to perform on the objects in the container) it usually doesn't impact you too much. Things either work or they don't. The iterator categories become important when: 1. You use some of the fancier built-in iterator types that will be demonstrated shortly. Or you graduate to creating your own iterators (this will also be demonstrated, later in this chapter). 2. You use the STL algorithms (the subject of the next chapter). Each of the algorithms have requirements that they place on the iterators that they work with. Knowledge of the iterator categories is even more important when you create your own reusable algorithm templates, because the iterator category that your algorithm requires determines how flexible the algorithm will be. If you only require the most primitive iterator category (input or output) then your algorithm will work with everything (copy( ) is an example of this). Predefined iterators The STL has a predefined set of iterator classes that can be quite handy for programs. For example, you've already seen reverse_iterator (produced by calling rbegin( ) and rend( ) for all the basic containers). The insertion iterators are necessary because some of the STL algorithms - copy( ) for example - use the assignment operator= in order to place objects in the destination container. This is a problem when you're using the algorithm to fill the container rather than to overwrite items that are already in the destination container. That is, when the space isn't already there. What the insert iterators do is change the implementation of the operator= so that instead of doing an assignment, it calls a push or insert function for that container, thus causing it to allocate new space. The shorthand functions back_inserter( ) and front_inserter( ) produce the same objects with a little less typing. Since all the basic sequence containers support push_back( ), you will probably find yourself using back_inserter( ) with some regularity. The insert_iterator allows you to insert elements in the middle of the sequence, again replacing the meaning of operator=, but this time with insert( ) instead of one of the push functions. The insert( ) member function requires an iterator indicating the place to insert before, so the insert_iterator requires that in addition to the basic sequence container object. The shorthand function inserter( ) produces the same object. However, you can see that vector does support the other two types of insertion (even though, as you shall see later, insert( ) is not a very efficient operation for vector). IO stream iterators You've already seen quite a bit of use of the ostream_iterator (an output iterator) in conjuction with copy( ) to place the contents of a container on an output stream. There is a corresponding istream_iterator (an input iterator) which allows you to iterate a set of objects of a specified type from an input stream. An important difference between ostream_iterator and istream_iterator comes from the fact that an output stream doesn't have any concept of an end, since you can always just keep writing more elements. However, an input stream eventually terminates (for example, when you reach the end of a file) so there needs to be a way to represent that. An istream_iterator has two constructors, one that takes an istream and produces the iterator you actually read from, and the other which is the default constructor and produces an object which is the past-the-end sentinel. Because out is defined with a newline as its second argument, these assignments also cause a newline to be inserted along with each assignment. Instead, you can use the special iterators istreambuf_iterator and ostreambuf_iterator, which are designed strictly to move characters60. Although these are templates, the only template arguments they will accept are either char or wchar_t (for wide characters). The following example allows you to compare the behavior of the stream iterators vs. You'll virtually always want to use a streambuf iterator when using characters and streams, rather than a stream iterator. Manipulating raw storage This is a little more esoteric and is generally used in the implementation of other Standard Library functions, but it is nonetheless interesting. It is provided to enable algorithms to store their results into uninitialized memory. The interface is quite simple: the constructor takes an output iterator that is pointing to the raw memory (thus it is typically a pointer) and the operator= assigns an object into that raw memory. The template parameters are the type of the output iterator pointing to the raw storage, and the type of object that will be stored. That's why the pointer from the new array of char is cast to a Noisy*. The assignment operator forces the objects into the raw storage using the copy-constructor. Note that the explicit destructor call must be made for proper cleanup, and this also allows the objects to be deleted one at a time during container manipulation. The sequences keep the objects in whatever sequence that you establish (either by pushing the objects on the end or inserting them in the middle). Since all the sequence containers have the same basic goal (to maintain your order) they seem relatively interchangeable. However, they differ in the efficiency of their operations, so if you are going to manipulate a sequence in a particular fashion you can choose the appropriate container for those types of manipulations. So far in this book I have been using vector as a catch-all container. Basic sequence operations Using a template, the following example shows the operations that all the basic sequences (vector, deque or list) support. As you shall learn in the sections on the specific sequence containers, not all of these operations make sense for each basic sequence, but they are supported. You can also see that every container has begin( ) and end( ) methods that return iterators. The basicOps( ) function tests everything else (and in turn calls print( )), including a variety of constructors: default, copy-constructor, quantity and initial value, and beginning and ending iterators. There's an assignment operator= and two kinds of assign( ) member functions, one which takes a quantity and initial value and the other which take a beginning and ending iterator. All the basic sequence containers are reversible containers, as shown by the use of the rbegin( ) and rend( ) member functions. A sequence container can be resized, and the entire contents of the container can be removed with clear( ). To erase( ) a single element from the middle, use a iterator; to erase( ) a range of elments you use a pair of iterators. Although both list and deque support push_front( ) and pop_front( ), vector does not, so the only member functions that work with all three are push_back( ) and pop_back( ). The naming of the member function swap( ) is a little confusing, since there's also a non-member swap( ) algorithm that switches two elements of a container. The member swap( ), however, swaps everything in one container for another (of the same type), effectively swapping the containers themselves. There's also a non-member version of this function. The following sections on the seqence containers discuss the particulars of each type of container. This section will give a more in-depth look at vector. To achieve maximally-fast indexing and iteration, the vector maintains its storage as a single contiguous array of objects. This is a critical point to observe in understanding the behavior of vector. It means that indexing and iteration are lighting-fast, being basically the same as indexing and iterating over an array of objects. But it also means that inserting an object anywhere but at the end (that is, appending) is not really an acceptable operation for a vector. It also means that when a vector runs out of pre-allocated storage, in order to maintain its contiguous array it must allocate a whole new (larger) chunk of storage elsewhere and copy the objects to the new storage. This has a number of unpleasant side effects. Cost of overflowing allocated storage A vector starts by grabbing a block of storage, as if it's taking a guess at how many objects you plan to put in it. As long as you don't try to put in more objects than that everything is very rapid and efficient (note that if you do know how many objects to expect, you can pre-allocate storage using reserve( )). The id is initialized using the create counter inside the default constructor; the copy-constructor and assignment operator take their id values from the rvalue. Of course, with operator= the lvalue is already an initialized object so the old value of id is printed before it is overwritten with the id from the rvalue. NoisyGen produces a function object (since it has an operator( )) that is used to automatically generate Noisy objects during testing. NoisyReport is a type of class called a singleton, which is a design pattern (these are covered more fully in Chapter XX). Here, the goal is to make sure there is one and only one NoisyReport object, because it is responsible for printing out the results at program termination. It has a private constructor so no one else can make a NoisyReport object, and a single static instance of NoisyReport called nr. The only actual code is the destructor, which is called as the program exits and the static destructors are called; this destructor prints out the statistics captured by the static variables in Noisy. The one snag to this header file is the inclusion of the definitions for the statics at the end. If you include this header in more than one place in your project, you'll get multiple-definition errors at link time. Of course, you can put the static definitions in a separate CPP file and link it in, but that is less convenient and since Noisy is just intended for quick-and-dirty experiments the header file should be reasonable for most situations. When you run this program, you'll see single default constructor call (for n), then a lot of copy-constructor calls, then some destructor calls, then some more copy-constructor calls, and so on. You can imagine that if you store a lot of large and complex objects, this process could rapidly become prohibitive. There are two solutions to this problem. The nicest one requires that you know beforehand how many objects you're going to make. However, in the more general case you won't know how many objects you'll need. If vector reallocations are slowing things down, you can change sequence containers. You could use a list, but as you'll see, the deque allows speedy insertions at either end of the sequence, and never needs to copy or destroy objects as it expands its storage. Of course, you don't want to program this way habitually, just be aware of these issues (avoid premature optimization). There is a darker side to vector's reallocation of memory, however. Because vector keeps its objects in a nice, neat array (allowing, for one thing, maximally-fast random access), the iterators used by vector are generally just pointers. This is a good thing - these pointers allow the fastest selection and manipulation of any of the sequence containers. However, consider what happens when you're holding onto an iterator (i.e. a pointer) and then you add the one additional object that causes the vector to reallocate storage and move it elsewhere. This is the way vector has been used in the book up to this point. You may observe that using vector as the basic container the book in earlier chapters may not be the best choice in all cases. This is a fundamental issue in containers, and in data structures in general: the best choice varies according to the way the container is used. The reason vector has been the best choice up until now is that it looks a lot like an array, and was thus familiar and easy for you to adopt. But from now on it's also worth thinking about other issues when choosing containers. Inserting and erasing elements The vector is most efficient if: 1. You reserve( ) the correct amount of storage at the beginning so the vector never has to reallocate. 2. You only add and remove elements from the back end. The generate_n( ) call is pretty busy: each call to NoisyGen::operator( ) results in a construction, a copy-construction (into the vector) and a destruction of the temporary. When an object is erased from the vector, the assignment operator is once again used to move everything up to cover the place that is being erased (notice that this requires that the assignment operator properly cleans up the lvalue). Lastly, the object on the end of the array is deleted. You can imagine how enormous the overhead can become if objects are inserted and removed from the middle of a vector if the number of elements is large and the objects are complicated. It's obviously a practice to avoid. However, it does not have vector's constraint of keeping everything in a single sequential block of memory. Instead, deque uses multiple blocks of sequential storage (keeping track of all the blocks and their order in a mapping structure). For this reason the overhead for a deque to add or remove elements at either end is very low. In addition, it never needs to copy and destroy contained objects during a new storage allocation (like vector does) so it is far more efficient than vector if you are adding an unknown quantity of objects. This means that vector is the best choice only if you have a pretty good idea of how many objects you need. In addition, many of the programs shown earlier in this book that use vector and push_back( ) might be more efficient with a deque. The interface to deque is only slightly different from a vector (deque has a push_front( ) and pop_front( ) while vector does not, for example) so converting code from using vector to using deque is almost trivial. Consider Strvector.cpp, which can be changed to the use of deque by replacing the word vector with deque everywhere. It's not so dramatic, is it? This points out some important points: 1. We (programmers) are typically very bad at guessing where inefficiencies occur in our programs. 2. Efficiency comes from a combination of effects - here, reading the lines in and converting them to strings may dominate over the cost of the vector vs. 3. The string class is probably fairly well-designed in terms of efficiency. Of course, this doesn't mean you shouldn't use a deque rather than a vector when you know that an uncertain number of objects will be pushed onto the end of the container. On the contrary, you should. But you should also be aware that performance issues are usually not where you think they are, and the only way to know for sure where your bottlenecks are is by testing. Later in this chapter there will be a more pure comparison of performance between vector, deque and list. Converting between sequences Sometimes you need the behavior or efficiency of one kind of container for one part of your program, and a different container's behavior or efficiency in another part of the program. For example, you may need the efficiency of a deque when adding objects to the container but the efficiency of a vector when indexing them. What's interesting is that v1 does not cause multiple allocations while building the vector, no matter how many elements you use. You might initially think that you must follow the process used for v2 and preallocate the storage to prevent messy reallocations, but apparently the constructor used for v1 determines the memory need ahead of time so this is unnecessary. Since the deque allocates all its storage in blocks instead of a contiguous array like vector, it never needs to move existing storage (thus no additional copy-constructions and destructions occur). It simply allocates a new block. For the same reason, the deque can just as efficiently add elements to the beginning of the sequence, since if it runs out of storage it (again) just allocates a new block for the beginning. Insertions in the middle of a deque, however, could be even messier than for vector (but not as costly). Because a deque never moves its storage, a held iterator never becomes invalid when you add new things to a deque, as it was demonstrated to do with vector (in VectorCoreDump.cpp). Secondly, calling insert( ) repeatedly with the same iterator would not ordinarily cause an access violation, but the iterator is walked forward after each insertion. I'm guessing it eventually walks off the end of a block, but I'm not sure what actually causes the problem. Even then the traversal is significantly slower than either a vector or a deque, but if you aren't doing a lot of traversals that won't be your bottleneck. Another thing to be aware of with a list is the memory overhead of each link, which requires a forward and backward pointer on top of the storage for the actual object. The objects in a list never move after they are created; moving a list element means changing the links, but never copying or assigning. This means that a held iterator never moves when you add new things to a list as it was demonstrated to do in vector. However, notice that sort( ) and reverse( ) are member functions of list, so they have special knowledge of the internals of list and can perform the pointer movement instead of copying. On the other hand, the swap( ) function is a generic algorithm, and doesn't know about list in particular and so it uses the copying approach for swapping two elements. The generic sort( ) and reverse( ) should only be used with vector and deque objects. Special list operations The list has some special operations that are built-in to make the best use of the structure of the list. After filling four lists with Noisy objects, one list is spliced into another in three different ways. In the first, the entire list l2 is spliced into l1 at the iterator it1. Notice that after the splice, l2 is empty - splicing means removing the elements from the source list. The second splice inserts elements from l3 starting at it2 into l1 starting at it1. The output from the code that demonstrates remove( ) shows that the list does not have to be sorted in order for all the elements of a particular value to be removed. Finally, if you merge( ) one list with another, the merge only works sensibly if the lists have been sorted. What you end up with in that case is a sorted list containing all the elements from both lists (the source list is erased - that is, the elements are moved to the destination list). This means that if you want all the duplicate elements removed you should sort( ) the list first. But if you want a sorted list with no duplicates, a set can give you that, right? This is reassuring - after all, it is set's primary job description! Swapping all basic sequences It turns out that all basic sequences have a member function swap( ) that's designed to switch one sequence with another (however, this swap( ) is only defined for sequences of the same type). Trying to move forward to the next link from an invalid link is poorly-formed code. Notice that the operation that broke deque in DequeCoreDump.cpp is perfectly fine with a list. Performance comparison To get a better feel for the differences between the sequence containers, it's illuminating to race them against each other while performing various operations. Each test is represented by a class that is templatized on the container it will operate on. The repeat count is just a factor, and all tests are compared using the same value. Each test class also has a member function that returns its name, so that it can easily be printed. You might think that this should be accomplished using run-time type identification, but since the actual name of the class involves a template expansion, this is actually the more direct approach. The measureTime( ) function template takes as its first template argument the operation that it's going to test - which is itself a class template selected from the group defined previously in the listing. Since the template argument Op will contain not only the name of the class, but also (mangled into it) the type of the container it's working with. The RTTI typeid( ) operation allows the name of the class to be extracted as a char*, which can then be used to create a string called id. This string can be searched using string::find( ) to look for deque, list or vector. The bool variable that corresponds to the string that matches becomes true, and this is used to properly initialize the cont string so the container name can be accurately printed, along with the test name. Once the type of test and the container being tested has been printed out, the actual test is quite simple. The Standard C library function clock( ) is used to capture the starting and ending CPU ticks (this is typically more fine-grained than trying to measure seconds). Since f is an object of type Op, which is a class that has an operator( ), the line: f(c, count); is actually calling the operator( ) for the object f. In main( ), you can see that each different type of test is run on each type of container, except for the containers that don't support the particular operation being tested (these are commented out). When you run the program, you'll get comparitive performance numbers for your particular compiler and your particular operating system and platform. The first two examples in this chapter used sets. Consider the problem of creating an index for a book. You might like to start with all the words in the book, but you only want one each and you want them sorted. Of course, a set is perfect for this, and solves the problem effortlessly as shown at the beginning of this chapter. However, there's also the problem of punctuation and any other non-alpha characters, which must be stripped off to generate proper words. It replaces the delimiter with a zero, and returns the address of the beginning of the token. If you call it subsequent times with a first argument of zero it will continue extracting tokens from the rest of the string until it finds the end. In this case, the delimiters are those that delimit the keywords and identifiers of C++, so it extracts these keywords and identifiers. Each word is turned into a string and placed into the wordlist vector, which eventually contains the whole file, broken up into words. You don't have to use a set just to get a sorted sequence. You can use the sort( ) function (along with a multitude of other functions in the STL) on different STL containers. However, it's likely that set will be faster. Eliminating strtok( ) Some programmers consider strtok( ) to be the poorest design in the Standard C library because it uses a static buffer to hold its data between function calls. This produces both wastefully-sized buffers, and lines longer than the longest line. This can also introduce security holes. For all these reasons it seems like a good idea to find an alternative for strtok( ). This iterator extracts information character-by-character from a stream. Although the istreambuf_iterator template argument might suggest to you that you could extract, for example, ints instead of char, that's not the case. The argument must be of some character type - a regular char or a wide character. After the file is open, an istreambuf_iterator called p is attached to the istream so characters can be extracted from it. The while loop reads words until the end of the input stream is found. This is detected using the default constructor for istreambuf_iterator which produces the past-the-end iterator object end. Thus, if you want to make sure you're not at the end of the stream, you simply say p!= end. The second type of iterator that's used here is the insert_iterator, which creates an iterator that knows how to insert objects into a container. Here, the container is the string called word, which behaves enough like a container for the purposes of insert_iterator. The constructor for insert_iterator requires the container and an iterator indicating where it should start inserting the characters. There is also a front_insert_iterator and back_insert_iterator, but those require that the container have a push_front( ) and push_back( ), respectively. The string class has neither. After the while loop sets everything up, it begins by looking for the first alpha character, and incrementing start until that character is found. Then it copies characters from one iterator to the other, stopping when a non-alpha character is found. Each word, assuming it is non-empty, is added to wordlist. StreamTokenizer: a more flexible solution The above program parses its input into strings of words containing only alpha characters, but that's still a special case compared to the generality of strtok( ). What we'd like now is an actual replacement for strtok( ) so we're never tempted to use it. This isn't as bad as it first seems, since a string can be turned into an istream via an istringstream. But in the next section we'll come up with the most general, reusable tokenizing tool, and this should give you a feeling of what reusable really means, and the effort necessary to create truly reusable code. A completely reusable tokenizer Since the STL containers and algorithms all revolve around iterators, the most flexible solution will itself be an iterator. You could think of the TokenIterator as an iterator that wraps itself around any other iterator that can produce characters. Because it is designed as an input iterator (the most primitive type of iterator) it can be used with any STL algorithm. Not only is it a useful tool in itself, the TokenIterator is also a good example of how you can design your own iterators.61 The TokenIterator is doubly flexible: first, you can choose the type of iterator that will produce the char input. Second, instead of just saying what characters represent the delimiters, TokenIterator will use a predicate which is a function object whose operator( ) takes a char and decides if it should be in the token or not. Although the two examples given here have a static concept of what characters belong in a token, you could easily design your own function object to change its state as the characters are read, producing a more sophisticated parser. It might appear that there's some kind of functionality that comes with std::iterator, but it is purely a way of tagging an iterator so that a container that uses it knows what it's capable of. Apart from the tagging, std::iterator doesn't do anything else, which means you must design all the other functionality in yourself. TokenIterator may look a little strange at first, because the first constructor requires both a begin and end iterator as arguments, along with the predicate. Remember that this is a wrapper iterator that has no idea of how to tell whether it's at the end of its input source, so the ending iterator is necessary in the first constructor. The reason for the second (default) constructor is that the STL algorithms (and any algorithms you write) need a TokenIterator sentinel to be the past-the-end value. Since all the information necessary to see if the TokenIterator has reached the end of its input is collected in the first constructor, this second constructor creates a TokenIterator that is merely used as a placeholder in algorithms. The core of the behavior happens in operator++. This erases the current value of word using string::resize( ), then finds the first character that satisfies the predicate (thus discovering the beginning of the new token) using find_if( ). The resulting iterator is assigned to first, thus moving first forward to the beginning of the token. Then, as long as the end of the input is not reached and the predicate is satisfied, characters are copied into the word from the input. Finally the new token is returned. The postfix increment requires a proxy object to hold the value before the increment, so it can be returned (see the operator overloading chapter for more details of this). Producing the actual value is a straightforward operator*. The only other functions that must be defined for an output iterator are the operator== and operator!= to indicate whether the TokenIterator has reached the end of its input. You can see that the argument for operator== is ignored - it only cares about whether it has reached its internal last iterator. Other than that, a TokenIterator works like any other input iterator. This, in my opinion, is an unfortunate case of confusing what something does with the details of its underlying implementation - the fact that these are adapters is of primary value only to the creator of the library. When you use them, you generally don't care that they're adapters, but instead that they solve your problem. Admittedly there are times when it's useful to know that you can choose an alternate implementation or build an adapter from an exisiting container object, but that's generally one level removed from the adapter's behavior. So, while you may see it emphasized elsewhere that a particular container is an adapter, I shall only point out that fact when it's useful. Note that each type of adapter has a default container that it's built upon, and this default is the most sensible implementation, so in most cases you won't need to concern yourself with the underlying implementation. When you call pop( ) it returns void rather than the top element, as you might expect. If you want the top element, you get a reference to it with top( ). It turns out this is more efficient, since a traditional pop( ) would have to return a value rather than a reference, and thus invoke the copy-constructor. The stack template has a very simple interface, essentially the member functions you see above. It doesn't have sophisticated forms of initialization or access, but if you need that you can use the underlying container that the stack is implemented upon. For example, suppose you have a function that expects a stack interface but in the rest of your program you need the objects stored in a list. The Line class determines the number of leading spaces, then stores the contents of the line without the leading spaces. You cannot iterate through a stack; this emphasizes that you only want to perform stack operations when you create a stack. You can get equivalent stack functionality using a vector and its back( ), push_back( ) and pop_back( ) methods, and then you have all the additional functionality of the vector. Of course, list has the aditional ability to push things at the front, but it's generally less efficient than using push_back( ) with vector. Functionally, you could use a deque anywhere you need a queue, and you would then also have the additional functionality of the deque. The only reason you need to use a queue rather than a deque, then, is if you want to emphasize that you will only be performing queue-like behavior. The queue is an adapter class like stack, in that it is built on top of another sequence container. As you might guess, the ideal implementation for a queue is a deque, and that is the default template argument for the queue; you'll rarely need a different implementation. Queues are often used when modeling systems where some elements of the system are waiting to be served by other elements in the system. A classic example of this is the bank-teller problem, where you have customers arriving at random intervals, getting into a line, and then being served by a set of tellers. Since the customers arrive randomly and each take a random amount of time to be served, there's no way to deterministically know how long the line will be at any time. However, it's possible to simulate the situation and see what happens. A problem in performing this simulation is the fact that, in effect, each customer and teller should be run by a separate process. What we'd like is a multithreaded environment, then each customer or teller would have their own thread. However, Standard C++ has no model for multithreading so there is no standard solution to this problem. On the other hand, with a little adjustment to the code it's possible to simulate enough multithreading to provide a satisfactory solution to our problem. Multithreading means you have multiple threads of control running at once, in the same address space (this differs from multitasking, where you have different processes each running in their own address space). The trick is that you have fewer CPUs than you do threads (and very often only one CPU) so to give the illusion that each thread has its own CPU there is a time-slicing mechanism that says OK, current thread - you've had enough time. I'm going to stop you and go give time to some other thread. This automatic stopping and starting of threads is called pre-emptive and it means you don't need to manage the threading process at all. An alternative approach is for each thread to voluntarily yield the CPU to the scheduler, which then goes and finds another thread that needs running. So instead, we'll build the time-slicing into the classes in the system. In this case, it will be the tellers that represent the threads, (the customers will be passive) so each teller will have an infinite-looping run( ) method that will execute for a certain number of time units, and then simply return. By using the ordinary return mechanism, we eliminate the need for any swapping. Of course, the amount of service time will be different for each customer, and will be determined randomly. In addition, you won't know how many customers will be arriving in each interval, so this will also be determined randomly. Notice that if the Teller has more time after finishing with a Customer, it gets a new customer and recurses into itself. Just as with a stack, when you use a queue, it's only a queue and doesn't have any of the other functionality of the basic sequence containers. This includes the ability to get an iterator in order to step through the stack. The driver for the simulation is the infinite while loop in main( ). At the beginning of each pass through the loop, a random number of customers are added, with random service times. Both the number of tellers and the queue contents are displayed so you can see the state of the system. After running each teller, the display is repeated. At this point, the system adapts by comparing the number of customers and the number of tellers; if the line is too long another teller is added and if it is short enough a teller can be removed. It is in this adaptation section of the program that you can experiment with policies regarding the optimal addition and removal of tellers. If this is the only section that you're modifying, you may want to encapsulate policies inside of different objects. Priority queues When you push( ) an object onto a priority_queue, that object is sorted into the queue according to a function or function object (you can allow the default less template to supply this, or provide one of your own). The priority_queue ensures that when you look at the top( ) element it will be the one with the highest priority. When you're done with it, you call pop( ) to remove it and bring the next one into place. Thus, the priority_queue has nearly the same interface as a stack, but it behaves differently. Like stack and queue, priority_queue is an adapter which is built on top of one of the basic sequences - the default is vector. When you run this program you'll see that duplicates are allowed, and the highest values appear first. If you look at the description for priority_queue, you see that the constructor can be handed a Compare object, as shown above. If you don't use your own Compare object, the default template behavior is to use the default constructor. While it is generally reasonable to expect ordinary classes to behave polymorphically, you cannot make this assumption when using the STL. Of course, a priority_queue of int is trivial. The output is: A1: Water lawn A2: Feed dog B1: Feed cat B7: Feed bird C3: Mow lawn C4: Empty trash Note that you cannot iterate through a priority_queue. However, it is possible to emulate the behavior of a priority_queue using a vector, thus allowing you access to that vector. This turns out to be reasonably straightforward, but you might think that a shortcut is possible. What you are seeing in the underlying vector is called a heap. This heap represents the tree of the priority queue (stored in the linear structure of the vector), but when you iterate through it you do not get a linear priority-queue order. You might think that you can simply call sort_heap( ), but that only works once, and then you don't have a heap anymore, but instead a sorted list. This means that to go back to using it as a heap the user must remember to call make_heap( ) first. The first for loop in main( ) now has the additional quality that it displays the heap as it's being built. The only drawback to this solution is that the user must remember to call sort( ) before viewing it as a sorted sequence (although one could concievably override all the methods that produce iterators so that they guarantee sorting). Holding bits Most of my computer education was in hardware-level design and programming, and I spent my first few years doing embedded systems development. Because C was a language that purported to be close to the hardware, I have always found it dismaying that there was no native binary representation for numbers. Decimal, of course, and hexadecimal (tolerable only because it's easier to group the bits in your mind), but octal? Whenever you read specs for chips you're trying to program, they don't describe the chip registers in octal, or even hexadecimal - they use binary. And yet C won't let you say 0b0101101, which is the obvious solution for a language close to the hardware. The primary differences between these types are: 1. The bitset holds a fixed number of bits. You establish the quantity of bits in the bitset template argument. 2. The bitset is explicitly designed for performance when manipulating bits, not to be a regular container. As such, it has no iterators and it's most storage-efficient when it contains an integral number of long values. A bitset provides virtually any bit operation that you could ask for, in a very efficient form. However, each bitset is made up of an integral number of longs (typically 32 bits), so even though it uses no more space than it needs, it always uses at least the size of a long. This means you'll use space most efficiently if you increase the size of your bitsets in chunks of the number of bits in a long. In addition, the only conversion from a bitset to a numerical value is to an unsigned long, which means that 32 bits (if your long is the typical size) is the most flexible form of a bitset. The following example tests almost all the functionality of the bitset (the missing operations are redundant or trivial). You'll see the description of each of the bitset outputs to the right of the output so that the bits all line up and you can compare them to the source values. If you still don't understand bitwise operations, running this program should help. The generated number and each new 16 bits is combined using the operator|=. The first thing demonstrated in main( ) is the unit size of a bitset. If it is less than 32 bits, sizeof produces 4 (4 bytes = 32 bits), which is the size of a single long on most implementations. If it's between 32 and 64, it requires two longs, greater than 64 requires 3 longs, etc. Thus you make the best use of space if you use a bit quantity that fits in an integral number of longs. However, notice there's no extra overhead for the object - it's as if you were hand-coding to use a long. Another clue that bitset is optimized for longs is that there is a to_ulong( ) member function that produces the value of the bitset as an unsigned long. There are no other numerical conversions from bitset, but there is a to_string( ) conversion that produces a string containing ones and zeros, and this can be as long as the actual bitset. There's still no primitive format for binary values, but the next best thing is supported by bitset: a string of ones and zeros with the least-significant bit (lsb) on the right. The three constructors demonstrated show taking the entire string (the char array is automatically converted to a string), the string starting at character 2, and the string from character 2 through 11. You'll notice that bitset only has three non-member operators: AND (and), OR (|) and EXCLUSIVE-OR (^). Each of these create a new bitset as their return value. All of the member operators opt for the more efficient and=, |=, etc. However, these forms actually change their lvalue (which is a in most of the tests in the above example). To prevent this, I created a temporary to be used as the lvalue by invoking the copy-constructor on a; this is why you see the form BS(a). The result of each test is printed out, and occasionally a is reprinted so you can easily look at it for reference. The rest of the example should be self-explanatory when you run it; if not you can find the details in your compiler's documentation or the other documentation mentioned earlier in this chapter. The only member function that was added to those already in vector is flip( ), to invert all the bits; there is no set( ) or reset( ) as in bitset. Of course, you must know the size of the bitset at compile-time. You can see that this conversion is not the kind of operation you'll want to do on a regular basis. Associative containers The set, map, multiset and multimap are called associative containers because they associate keys with values. Well, at least maps and multimaps associate keys to values, but you can look at sets as maps that have no values, only keys (and they can in fact be implemented this way), and the same for the relationship between multisets and multimaps. So, because of the structural similarity sets and multisets are lumped in with associative containers. The most important basic operations with associative containers are putting things in, and in the case of a set, seeing if something is in the set. In the case of a map, you want to first see if a key is in the map, and if it exists you want the associated value for that key to be returned. Of course, there are many variations on this theme but that's the fundamental concept. The find( ) member function will produce an iterator indicating the first occurrence (with set and map, the only occurrence) of the key that you give it, or the past-the-end iterator if it can't find the key. The count( ) and find( ) member functions exist for all the associative containers, which makes sense. But what happens if you decide to index out of the bounds of the array? One option, of course, is to throw an exception, but with a map indexing out of the array could mean that you want an entry there, and that's the way the STL map treats it. This means that if you really just want to look something up and not create a new entry, you must use count( ) (to see if it's there) or find( ) (to get an iterator to it). First, it requires integral keys (which we happen to have in this case). Finally, if you look at the output from the for loop you'll see that things are very busy, and it's quite puzzling at first why there are so many constructions and destructions for what appears to be a simple lookup. All it really does it package together two objects, but it's very useful, especially when you want to return two objects from a function (since a return statement only takes one object). There's even a shorthand for creating a pair called make_pair( ), which is used further down in AssociativeBasics.cpp. So to retrace the steps, map::value_type is a pair of the key and the value of the map - actually, it's a single entry for the map. But notice that pair packages its objects by value, which means that copy-constructions are necessary to get the objects into the pair. Here, we're getting off easy because the key is an int. The return value of insert( ) is a different kind of pair, where first is an iterator pointing to the key-value pair that was just inserted, and second is a bool indicating whether the insertion took place. Here's where make_pair( ) comes in handy, as you can see. For looking objects up in a map, you can use count( ) to see whether a key is in the map, or you can use find( ) to produce an iterator pointing directly at the key-value pair. Again, since the map contains pairs that's what the iterator produces when you dereference it, so you have to select first and second. When you run AssociativeBasics.cpp you'll notice that the iterator approach involves no extra object creations or destructions at all. It's not as easy to write or read, though. Of course, you can also iterate through a set or map and operate on each of its objects. This will be demonstrated in later examples. However, these are implemented by using operator= to assign values into the sequential containers, and the way that you add objects to associative containers is with their respective insert( ) member functions. Thus the fill and generate functions do not work with associative containers. It would be useful to have functions like these for the associative containers, if for no other use than testing. Now you can fill an associative container with a value or using a generator. This demonstrates a valuable lesson: if the algorithms don't do what you want, copy the nearest thing and modify it. You have the example at hand in the STL header, so most of the work has already been done. The magic of maps An ordinary array uses an integral value to index into a sequential set of elements of some type. A map is an associative array, which means you associate one object with another in an array-like fashion, but instead of selecting an array element with a number as you do with an ordinary array, you look it up with an object! The example which follows counts the words in a text file, so the index is the string object representing the word, and the value being looked up is the object that keeps count of the strings. In a single-item container like a vector or list, there's only one thing being held. This is fine as long as you're using an array-style lookup, but what if you simply want to move through the entire map and list each key-value pair? Of course you use an iterator, like everything else in the STL, but since there are two items - the key and the value - which one should the iterator produce? Dereferencing a map iterator produces both items, packaged together into a single pair object (since a function can only return a single value). As a reminder, you access the members of a pair by selecting first or second. This same philosophy of packaging two items together is also used to insert elements into the map, but the pair is created as part of the instantiated map and is called value_type, containing the key and the value. So one option for inserting a new element is to create a value_type object, loading it with the appropriate objects and then calling the insert( ) member function for the map. If there isn't, the map automatically inserts a key for the word you're looking up, and a Count object, which is initialized to zero by the default constructor. Thus, when it's incremented the Count becomes 1. As previously mentioned, dereferencing this iterator produces a pair object, with the first member the key and the second member the value. In this case second is a Count object, so it's val( ) member must be called to produce the actual word count. Multimaps and duplicate keys A multimap is a map that can contain duplicate keys. At first this may seem like a strange idea, but it can occur surprisingly often. A phone book, for example, can have many entries with the same name. Another example that uses a multimap is the ExtractCode.cpp program in Chapter XX. Suppose you are monitoring wildlife, and you want to keep track of where and when each type of animal is spotted. Thus, you may see many animals of the same kind, all in different locations and at different times. So if the type of animal is the key, you'll need a multimap. It uses the Standard C library time functions to record the time of the sighting. In the array of string animal, notice that the char* constructor is automatically used during initialization, which makes initializing an array of string quite convenient. The key-value pairs that make up a Sighting are the string which names the type of animal, and the DataPoint that says where and when it was sighted. The standard pair template combines these two types and is typedefed to produce the Sighting type. SightingGen generates random sightings at random data points to use for testing. A DataMap is a multimap of string-DataPoint pairs, which means it stores Sightings. At this point the user is asked to select an animal; this is the one they want to see all the sightings for. If you press 'q' the program will quite, but if you select an animal number, then the equal_range( ) member function is invoked. This returns an iterator (DMIter) to the beginning of the set of matching pairs, and one indicating past-the-end of the set. Since only one object can be returned from a function, the equal_range makes use of pair. Since the range pair has the beginning and ending iterators of the matching set, those iterators can be used in copy( ) to print out all the sightings for a particular type of animal. Multisets You've seen the set, which only allows one object of each value to be inserted. The multiset is odd by comparison since it allows more than one object of each value to be inserted. This seems to go against the whole idea of setness, where you can ask is 'it' in this set? If there can be more than one of 'it', then what does that question mean? Thus each duplicate object will have something that makes it unique from the other duplicates - most likely different state information that is not used in the calculation of the value during the comparison. That is, to the comparison operation, the objects look the same but they actually contain some differing internal state. Like any STL container that must order its elements, the multiset template uses the less template by default to determine element ordering. These values are displayed, then each one is used to produce the equal_range( ) in the multiset (equal_range( ) has the same meaning here as it does with multimap: all the elements with matching keys). Each set of matching keys is then printed. In the end, is this really a set, or should it be called something else? An alternative is the generic bag that has been defined in some container libraries, since a bag holds anything at all without discrimination - including duplicate objects. Besides, if you wanted to store a bunch of objects without any special criterions, you'd probably just use a vector, deque or list. Combining STL containers When using a thesaurus, you have a word and you want to know all the words that are similar. When you look up a word, then, you want a list of words as the result. Here, the multi containers (multimap or multiset) are not appropriate. The solution is to combine containers, which is easily done using the STL. A TEntry is a single entry in a Thesaurus. The ThesaurusGen creates words (which are just single letters) and synonyms for those words (which are just other randomly-chosen single letters) to be used as thesaurus entries. It randomly chooses the number of synonym entries to make, but there must be at least two. All the letters are chosen by indexing into a static string that is part of ThesaurusGen. In main( ), a Thesaurus is created, filled with 10 entries and printed using the copy( ) algorithm. Then the user is requested to choose a word to look up by typing the letter of that word. Because templates make the expression of powerful concepts easy, you can take this concept much further, creating a map of vectors containing maps, etc. For that matter, you can combine any of the STL containers this way. Cleaning up containers of pointers In Stlshape.cpp, the pointers did not clean themselves up automatically. It would be convenient to be able to do this easily, rather than writing out the code each time. Purging the same container twice is not a problem, because purge( ) sets the pointer to zero once it deletes that pointer, and calling delete for a zero pointer is a safe operation. Creating your own containers With the STL as a foundation, it's possible to create your own containers. Assuming you follow the same model of providing iterators, your new container will behave as if it were a built-in STL container. Consider the ring data structure, which is a circular sequence container. If you reach the end, it just wraps around to the beginning. The Ring iterator must know how to loop back to the beginning, so it must keep a reference to the list its parent Ring object in order to know if it's at the end and how to get back to the beginning. You'll notice that the interface for Ring is quite limited; in particular there is no end( ), since a ring just keeps looping. This means that you won't be able to use a Ring in any STL algorithms that require a past-the-end iterator - which is many of them. Although this can seem limiting, consider stack, queue and priority_queue, which don't produce any iterators at all! Freely-available STL extensions Although the STL containers may provide all the functionality you'll ever need, they are not complete. For example, the standard implementations of set and map use trees, and although these are reasonably fast they may not be fast enough for your needs. Fortunately, there are freely-available alternatives. One of the nice things about the STL is that it establishes a basic model for creating STL-like classes, so anything built using the same model is easy to understand if you are already familiar with the STL. Let's consider a performance comparison between a tree-based map and the SGI hash_map. If a profiler shows a bottleneck in your map, you should consider a hash_map. My higher hope is that this chapter has made you grasp the incredible power available in the STL, and shown you how much faster and more efficient your programming activities can be by using and understanding the STL. The fact that I could not escape from introducing some of the STL algorithms in this chapter suggests how useful they can be. In the next chapter you'll get a much more focused look at the algorithms. Error messages One of the greatest weaknesses of the STL, or more appropriately of C++ templates, will be shown to you when you try to write STL code and start getting compile-time error messages. When you're not used to it, the quantity of inscrutable text that will be barfed at you by the compiler will be quite overwhelming. The issue is that a template implies an interface. That is, even though the template keyword says I'll take any type, the code in a template definition actually requires that certain operators and member functions be supported - that's the interface. Compilers can only get so good at reporting template instantiation errors, so you'll have to grit your teeth, go to the first line reported as an error and figure it out. Exercises 1. Change StlShape.cpp so that it uses a deque instead of a vector. 2. Modify BankTeller.cpp so that the policy that decides when a teller is added or removed is encapsulated inside a class. 3. Rewrite Ring.cpp so it uses a deque instead of a list for its underlying implementation. 4. Modify Ring.cpp so that the underlying implementation can be chosen using a template argument (let that template argument default to list). The STL was originally designed around the algorithms. The goal was that you use algorithms for almost every piece of code that you write. In this sense it was a bit of an experiment, and only time will tell how well it works. The real test will be in how easy or difficult it is for the average programmer to adapt. At the end of this chapter you'll be able to decide for yourself whether you find the algorithms addictive or too confusing to remember. Let me know if you have questions. For example, consider the problem introduced in last chapter's Stlshape.cpp. Stroustrup3 (pg 531) provides the solution (which is different than the one shown here). However, you could also argue that transform( ) is less clear than simply creating an iterator and stepping through the container. This is part of the conundrum of using the algorithms - they are often so small that it might be clearer and simpler for your purposes just to write out the code rather than calling the algorithm. You'll need to make this decision yourself. Filling a container The generate algorithms were used frequently in the previous chapter. The fill( ), fill_n( ), generate( ) and generate_n( ) algorithms automatically insert objects into any sequence container (vector, list or deque - how about the stack, queue and priority_queue which are based on them?). The difference is that the fill functions insert a single value multiple times into the container, while the generate functions use an object called a generator to create the values to insert into the container. There are four interesting cases that effectively cover the kinds of things you'll probably encounter in your day-to-day programming: 1. A container of strings (that is, a container of a library-defined type). 2. A container of objects of a user-defined type. This demonstrates the operations your type must support in order to be used with the STL containers and algorithms. 3. An array of strings. This demonstrates that an array can be treated by the STL algorithms as if it were a container (because array pointers can be treated as iterators). 4. A string. Since a string can produce begin( ) and end( ) iterators, it can be treated as a container of char (or w_char). It takes any pair of iterators to containers of strings and sends the contents to cout. The first generates strings, and uses the StreamTokenizer developed in the last chapter. The second generates chars and is used to fill up a string (when treating a string as a sequence container). You'll note that the one member function that any generator implements is operator( ) (with no arguments). This is what is called by the generate functions. The UserDefined class is meant to represent a simple example of a user-defined type. Here, the synthesized copy-constructor and operator= are used, but note that they are necessary. Also, this is the simplest possible case, and you'll soon see that a user-defined type will generally have to be more complete in order to be used with the STL algorithms. The print( ) function simply provides a shorthand for printing a vector of UserDefined. The UDGenerator is for use with a generate algorithm. Again, it's quite simple, creating UserDefined objects with ascending index numbers. It is important to create this iterator because otherwise fill_n would try to insert at the front, which isn't legal for a vector. Since generate_n( ) also uses a back_inserter( ), its values will be appended to the end and you'll have a vector with 10 elements. The call to generate( ) overwrites those 10 values, and so does the call to fill( ). When you run the program you can see the results and verify these behaviors. The rest of the example repeats these operations with different types of containers. The function template doesn't care; all it wants is something it can dereference and move forward using operator++, and the array pointers fit the requirements. The string object can also fit these requirements by producing iterators to its begin and end. Other than that, it looks like any other container. Because the fill and generate functions do not extend to the associative containers (set, multiset, map and multimap) the assocFill_n( ) and assocGen_n( ) functions were created and demonstrated in the previous chapter. A test framework for the examples in this chapter For general demonstrations of all the algorithms, the previous example has some drawbacks: 1. It's bulky, and would use up too much paper if repeated across all the examples. 2. It's distracting, since most of the code is involved with setting up the example or printing the results, rather than using the algorithms. 3. The UserDefined class is not really typical of what you'll generally have to create to use it with the STL algorithms. In this section, a framework will be created to automatically fill the appropriate containers, to minimize the space and distraction of the previous example. This framework will be used with the rest of the examples in this chapter, and will define a new user-defined class to demonstrate the level of completeness necessary for such a class to be used with the STL algorithms. So instead of demonstrating the simplest possible examples, such as an array of int, the examples in the rest of the chapter will show the most general case, which is containers of a user-defined type. They will also demonstrate the operations your classes must support in order to be used with the various STL algorithms. Keep in mind, however, that all the algorithms may be applied as shown in the previous example: to containers of string, to arrays of any type, and to string objects themselves, treating the string as a container of characters. The class which will be created as the example will be reasonably complex: it's a bicycle which can have a choice of parts. In addition, you can change the parts during the lifetime of a Bicycle object; this includes the ability to add new parts or to upgrade from standard-quality parts to fancy parts. The cloning process, of course, will be more involved when there are data members in a BicyclePart. BicyclePart::partcount is used to keep track of the number of parts created and destroyed (so you can detect memory leaks). It is incremented every time a new BicyclePart is created and decremented when one is destroyed; also, when partcount goes to zero this is reported and if it goes below zero there will be an assert( ) failure. But whenever you remeove a part from a Bicycle, you must call delete for that pointer, otherwise it won't get cleaned up. But I hope you get the idea. I'm stuck on the LeakChecker, can you see where I've missed something? The specific references to compilers should be removed, for example. You hand this two iterators for the starting and ending points, and a pointer to a function that takes an argument of the same type that your iterators produce. So for_each( ) actually performs the operations that have been explicitly written out in most of the examples in this chapter. In addition, each Foo keeps a char* identifier to make tracking the output easier. The commented numbers next to the approaches for destruction correspond to the strings used to create the corresponding FooVector in main( ). Approach one is the simple pointer-to-function, which works but has the drawback that you must write a new Destroy function for each different type. The obvious solution is to make a template, but approach two shows that a template with an overloaded operator( ) will also work. On the other hand, approach three also makes sense: why not use a template function? Since this is obviously something you might want to do a lot, why not create an algorithm to delete all the pointers in a container? This was accomplished with the purge( ) template produced in the previous chapter. Summary Much of the time you will find yourself making relatively simple use of the STL. Either you'll just create containers of objects (as shown earlier, string is probably the most popular candidate), or you'll create containers of pointers to base classes to support polymorphic calls on groups of objects. The convenience, efficiency and reliability of the STL for these activities will certainly improve your programming productivity. However, the STL has powerful implications as a tool with which to create other tools, as was briefly shown in the STREDIT and FILELIST tools. It's as if the STL has turned C++ into a Very High Level Language by moving you away from the low level details. As a result, people are beginning to create some very potent tools. When you step into this realm you must begin to understand much more of the underlying structure of the STL; the learning curve from relatively simple usage to creating sophisticated tools is rather steep and shouldn't be taken lightly. Other good resources are Exercises Part 3: Advanced Topics 22: Multiple inheritance The basic concept of multiple inheritance (MI) sounds simple enough. The syntax is exactly what you'd expect, and as long as the inheritance diagrams are simple, MI is simple as well. However, MI can introduce a number of ambiguities and strange situations, which are covered in this chapter. But first, it helps to get a perspective on the subject. Perspective Before C++, the most successful object-oriented language was Smalltalk. Smalltalk was created from the ground up as an OO language. It is often referred to as pure, whereas C++, because it was built on top of C, is called hybrid. One of the design decisions made with Smalltalk was that all classes would be derived in a single hierarchy, rooted in a single base class (called Object - this is the model for the object-based hierarchy). So the Smalltalk class hierarchy is always a single monolithic tree. However, with C++ you can create as many hierarchy trees as you want. Therefore, for logical completeness the language must be able to combine more than one class at a time - thus the need for multiple inheritance. Since then, a number of other features have been added (notably templates) that change the way we think about programming and place MI in a much less important role. You can think of MI as a minor language feature that shouldn't be involved in your daily design decisions. One of the most pressing issues that drove MI involved containers. Suppose you want to create a container that everyone can easily use. One approach is to use void* as the type inside the container, as with PStash and Stack. The Smalltalk approach, however, is to make a container that holds Objects. Now consider the situation in C++. Suppose vendor A creates an object-based hierarchy that includes a useful set of containers including one you want to use called Holder. Now you come across vendor B's class hierarchy that contains some other class that is important to you, a BitImage class, for example, which holds graphic images. The only way to make a Holder of BitImages is to inherit a new class from both Object, so it can be held in the Holder, and BitImage: This was seen as an important reason for MI, and a number of class libraries were built on this model. However, as you saw in Chapter 14, the addition of templates has changed the way containers are created, so this situation isn't a driving issue for MI. The other reason you may need MI is logical, related to design. Unlike the above situation, where you don't have control of the base classes, in this one you do, and you intentionally use MI to make the design more flexible or useful. Regardless of what motivates you to use MI, a number of problems arise in the process, and you need to understand them to use it. Duplicate subobjects When you inherit from a base class, you get a copy of all the data members of that base class in your derived class. This copy is referred to as a subobject. If you multiply inherit from class d1 and class d2 into class mi, class mi contains one subobject of d1 and one of d2. So your mi object looks like this: Now consider what happens if d1 and d2 both inherit from the same base class, called Base: In the above diagram, both d1 and d2 contain a subobject of Base, so mi contains two subobjects of Base. Because of the path produced in the diagram, this is sometimes called a diamond in the inheritance hierarchy. Without diamonds, multiple inheritance is quite straightforward, but as soon as a diamond appears, trouble starts because you have duplicate subobjects in your new class. This takes up extra space, which may or may not be a problem depending on your design. But it also introduces an ambiguity. Ambiguous upcasting What happens, in the above diagram, if you want to cast a pointer to an mi to a pointer to a Base? There are two subobjects of type Base, so which address does the cast produce? First, you cannot even create the class mi because doing so would cause a clash between the two definitions of vf( ) in D1 and D2. The solution to the second problem is a language extension: The meaning of the virtual keyword is overloaded. If you inherit a base class as virtual, only one subobject of that class will ever appear as a base class. Virtual base classes are implemented by the compiler with pointer magic in a way suggesting the implementation of ordinary virtual functions. Because only one subobject of a virtual base class will ever appear during multiple inheritance, there is no ambiguity during upcasting. The most derived class and virtual base initialization The use of virtual base classes isn't quite as simple as that. The above example uses the (compiler-synthesized) default constructor. If the virtual base has a constructor, things become a bit strange. To understand this, you need a new term: most-derived class. The most-derived class is the one you're currently in, and is particularly important when you're thinking about constructors. In the previous example, Base is the most-derived class inside the Base constructor. Inside the D1 constructor, D1 is the most-derived class, and inside the MI constructor, MI is the most-derived class. When you are using a virtual base class, the most-derived constructor is responsible for initializing that virtual base class. That means any class, no matter how far away it is from the virtual base, is responsible for initializing it. But so must MI and X, even though they are more than one layer away! That's because each one in turn becomes the most-derived class. The compiler can't know whether to use D1's initialization of Base or to use D2's version. Thus you are always forced to do it in the most-derived class. Note that only the single selected virtual base constructor is called. Overhead The term pointer magic has been used to describe the way virtual inheritance is implemented. Because all these classes contain virtual functions, you expect the object size to be bigger than the core size by a pointer (at least - your compiler may also pad extra bytes into an object for alignment). But when virtual inheritance is added, it would appear that the VPTR plus two extra pointers are added! By the time the multiple inheritance is performed, the object appears to contain five extra pointers (however, one of these is probably a second VPTR for the second multiply inherited subobject). The curious can certainly probe into your particular implementation and look at the assembly language for member selection to determine exactly what these extra bytes are for, and the cost of member selection with multiple inheritance62. The rest of you have probably seen enough to guess that quite a bit more goes on with virtual multiple inheritance, so it should be used sparingly (or avoided) when efficiency is an issue. Upcasting When you embed subobjects of a class inside a new class, whether you do it by creating member objects or through inheritance, each subobject is placed within the new object by the compiler. Of course, each subobject has its own this pointer, and as long as you're dealing with member objects, everything is quite straightforward. But as soon as multiple inheritance is introduced, a funny thing occurs: An object can have more than one this pointer because the object represents more than one type during upcasting. Each class has a function that prints its this pointer, and these classes are assembled with both multiple inheritance and composition into the class MI, which prints its own address and the addresses of all the other subobjects. This function is called in main( ). You can clearly see that you get two different this pointers for the same object. The address of the MI object is taken and upcast to the two different types. The starting address of the object corresponds to the address of the first class in the base-class list. Then the second inherited class is placed, followed by the member objects in order of declaration. The only way things can work correctly is if this implicit upcasting takes place when you call a member function for a multiply inherited subobject. Persistence Normally this isn't a problem, because you want to call member functions that are concerned with that subobject of the multiply inherited object. However, if your member function needs to know the true starting address of the object, multiple inheritance causes problems. Ironically, this happens in one of the situations where multiple inheritance seems to be useful: persistence. The lifetime of a local object is the scope in which it is defined. The lifetime of a global object is the lifetime of the program. A persistent object lives between invocations of a program: You can normally think of it as existing on disk instead of in memory. Four issues arise when storing an object on disk: 4. The object must be converted from its representation in memory to a series of bytes on disk. 5. Because the values of any pointers in memory won't have meaning the next time the program is invoked, these pointers must be converted to something meaningful. 6. What the pointers point to must also be stored and retrieved. 7. When restoring an object from disk, the virtual pointers in the object must be respected. Because the object must be converted back and forth between a layout in memory and a serial representation on disk, the process is called serialization (to write an object to disk) and deserialization (to restore an object from disk). Although it would be very convenient, these processes require too much overhead to support directly in the language. Class libraries will often build in support for serialization and deserialization by adding special member functions and placing requirements on new classes. MI-based persistence Consider sidestepping the pointer issues for now and creating a class that installs persistence into simple objects using multiple inheritance. By inheriting the persistence class along with your new class, you automatically create classes that can be read from and written to disk. Although this sounds great, the use of multiple inheritance introduces a pitfall, as seen in the following example. A more sophisticated Persistent class would call a virtual write( ) function for each subobject. With the language features covered so far in the book, the number of bytes in the object cannot be known by the Persistent class so it is inserted as a constructor argument. And it works fine in class WData1 when, in main( ), it's written to file F1.DAT and later read back again. However, when Persistent is put second in the inheritance list in class WData1, the this pointer for Persistent is offset to the end of the object, so it reads and writes past the end of the object. This not only produces garbage when reading the object from the file, it's dangerous because it walks over any storage that occurs after the object. This problem occurs in multiple inheritance any time a class must produce the this pointer for the actual object from a subobject's this pointer. Of course, if you know your compiler always lays out objects in order of declaration in the inheritance list, you can ensure that you always put the critical class at the beginning of the list (assuming there's only one critical class). However, such a class may exist in the inheritance hierarchy of another class and you may unwittingly put it in the wrong place during multiple inheritance. Fortunately, using run-time type identification (the subject of Chapter 17) will produce the proper pointer to the actual object, even if multiple inheritance is used. The argument to the function is the stream object to write to or read from.64 Then the creator of the class, who knows best how the new parts should be read or written, is responsible for making the correct function calls. If you already knew that Data would be persistent, you could inherit directly from Persistent and redefine the functions there, thus eliminating the need for multiple inheritance. This example is based on the idea that you don't own the code for Data, that it was created elsewhere and may be part of another class hierarchy so you don't have control over its inheritance. However, for this scheme to work correctly you must have access to the underlying implementation so it can be stored; thus the use of protected. The classes WData1 and WData2 use familiar iostream inserters and extractors to store and retrieve the protected data in Data to and from the iostream object. In write( ), you can see that spaces are added after each floating point number is written; these are necessary to allow parsing of the data on input. The class Conglomerate not only inherits from Data, it also has member objects of type WData1 and WData2, as well as a pointer to a character string. In addition, all the classes that inherit from Persistent also contain a VPTR, so this example shows the kind of problem you'll actually encounter when using persistence. When you create write( ) and read( ) function pairs, the read( ) must exactly mirror what happens during the write( ), so read( ) pulls the bits off the disk the same way they were placed there by write( ). Here, the first problem that's tackled is the char*, which points to a string of any length. The size of the string is calculated and stored on disk as an int (followed by a space to enable parsing) to allow the read( ) function to allocate the correct amount of storage. When you have subobjects that have read( ) and write( ) member functions, all you need to do is call those functions in the new read( ) and write( ) functions. This is followed by direct storage of the members in the base class. People have gone to great lengths to automate persistence, for example, by creating modified preprocessors to support a persistent keyword to be applied when defining a class. Avoiding MI The need for multiple inheritance in PERSIST2.CPP is contrived, based on the concept that you don't have control of some of the code in the project. Upon examination of the example, you can see that MI can be easily avoided by using member objects of type Data, and putting the virtual read( )and write( ) members inside Data or WData1 and WData2 rather than in a separate class. There are many situations like this one where multiple inheritance may be avoided; the language feature is included for unusual, special-case situations that would otherwise be difficult or impossible to handle. 2. Do I need to upcast to both of the base classes? One situation to watch for is when one class only needs to be upcast as a function argument. In that case, the class can be embedded and an automatic type conversion operator provided in your new class to produce a reference to the embedded object. Any time you use an object of your new class as an argument to a function that expects the embedded object, the type conversion operator is used. However, type conversion can't be used for normal member selection; that requires inheritance. Repairing an interface One of the best arguments for multiple inheritance involves code that's out of your control. Suppose you've acquired a library that consists of a header file and compiled member functions, but no source code for member functions. This library is a class hierarchy with virtual functions, and it contains some global functions that take pointers to the base class of the library; that is, it uses the library objects polymorphically. Now suppose you build an application around this library, and write your own code that uses the base class polymorphically. If you had the source code, you could go back and put it in. But you don't, and you have a lot of existing code that depends on the original interface. Here, multiple inheritance is the perfect solution. Notice that it also includes the functions A( ) and B( ), which take a base pointer and treat it polymorphically. Instead, you get a compiled file as VENDOR.OBJ or VENDOR.LIB (or the equivalent for your system). The problem occurs in the use of this library. First, the destructor isn't virtual. This is actually a design error on the part of the library creator. In addition, f( ) was not made virtual; assume the library creator decided it wouldn't need to be. And you discover that the interface to the base class is missing a function essential to the solution of your problem. Also suppose you've already written a fair amount of code using the existing interface (not to mention the functions A( ) and B( ), which are out of your control), and you don't want to change it. Now each of the derived classes in the original library must be recreated, mixing in the new interface with MI. The functions Paste1::v( ) and Paste1::f( )need to call only the original base-class versions of their functions. Also, the new interface function g( ) can be called through mp. The destructor is now virtual and exhibits the correct behavior. Although this is a messy example, it does occur in practice and it's a good demonstration of where multiple inheritance is clearly necessary: You must be able to upcast to both base classes. Summary The reason MI exists in C++ and not in other OOP languages is that C++ is a hybrid language and couldn't enforce a single monolithic class hierarchy the way Smalltalk does. Instead, C++ allows many inheritance trees to be formed, so sometimes you may need to combine the interfaces from two or more trees into a new class. If no diamonds appear in your class hierarchy, MI is fairly simple (although identical function signatures in base classes must be resolved). If a diamond appears, then you must deal with the problems of duplicate subobjects by introducing virtual base classes. This not only adds confusion, but the underlying representation becomes more complex and less efficient. Multiple inheritance has been called the goto of the 90's.65 This seems appropriate because, like a goto, MI is best avoided in normal programming, but can occasionally be very useful. It's a minor but more advanced feature of C++, designed to solve problems that arise in special situations. If you find yourself using it often, you may want to take a look at your reasoning. A good Occam's Razor is to ask, Must I upcast to all of the base classes? If not, your life will be easier if you embed instances of all the classes you don't need to upcast to. Exercises 1. These exercises will take you step-by-step through the traps of MI. Create a base class X with a single constructor that takes an int argument and a member function f( ), that takes no arguments and returns void. Now inherit X into Y and Z, creating constructors for each of them that takes a single int argument. Now multiply inherit Y and Z into A. Create an object of class A, and call f( ) for that object. Fix the problem with explicit disambiguation. 2. Starting with the results of exercise 1, create a pointer to an X called px, and assign to it the address of the object of type A you created before. Fix the problem using a virtual base class. Now fix X so you no longer have to call the constructor for X inside A. 3. Starting with the results of exercise 2, remove the explicit disambiguation for f( ), and see if you can call f( ) through px. Trace it to see which function gets called. Fix the problem so the correct function will be called in a class hierarchy. Some of the reason is no doubt the tediousness and code bloat of checking for many errors. For example, printf( ) returns the number of arguments that were successfully printed, but virtually no one checks this value. The proliferation of code alone would be disgusting, not to mention the difficulty it would add in reading the code. The problem with C's approach to error handling could be thought of as one of coupling - the user of a function must tie the error-handling code so closely to that function that it becomes too ungainly and awkward to use. One of the major features in C++ is exception handling, which is a better way of thinking about and handling errors. With exception handling, 4. Error-handling code is not nearly so tedious to write, and it doesn't become mixed up with your normal code. You write the code you want to happen; later in a separate section you write the code to cope with the problems. If you make multiple calls to a function, you handle the errors from that function once, in one place. 5. Errors cannot be ignored. If a function needs to send an error message to the caller of that function, it throws an object representing that error out of the function. If the caller doesn't catch the error and handle it, it goes to the next enclosing scope, and so on until someone catches the error. Error handling in C Until Chapter 7, this book used the Standard C library assert( ) macro as a shorthand for error handling. After Chapter 7, assert( ) was used as it was intended: for debugging during development with code that could be disabled with #define NDEBUG for the shipping product. For run-time error checking, assert( ) was replaced by the require( ) functions and macros developed in Chapter 10. Error handling is quite straightforward in situations where you check some condition and you know exactly what to do because you have all the necessary information in that context. Of course, you just handle the error at that point. These are ordinary errors and not the subject of this chapter. The problem occurs when you don't have enough information in that context, and you need to pass the error information into a larger context where that information does exist. There are three typical approaches in C to handle this situation. 6. Return error information from the function or, if the return value cannot be used this way, set a global error condition flag. In addition, returning from a function that hits an exceptional condition may not make sense. 7. Use the little-known Standard C library signal-handling system, implemented with the signal( ) function (to determine what happens when the event occurs) and raise( ) (to generate an event). 8. Use the nonlocal goto functions in the Standard C library: setjmp( ) and longjmp( ). With setjmp( ) you save a known good state in the program, and if you get into trouble, longjmp( ) will restore that state. Again, there is high coupling between the place where the state is stored and the place where the error occurs. This makes it virtually impossible to effectively recover from an exceptional condition because you'll always leave objects behind that haven't been cleaned up and that can no longer be accessed. In that case it has the behavior of an ordinary function. However, if you call longjmp( ) using the same jmp_buf, it's as if you're returning from setjmp( ) again - you pop right out the back end of the setjmp( ). This time, the value returned is the second argument to longjmp( ), so you can detect that you're actually coming back from a longjmp( ). You can imagine that with many different jmp_bufs, you could pop around to many different places in the program. This is called throwing an exception. Here's what it looks like: throw myerror(something bad happened); myerror is an ordinary class, which takes a char* as its argument. You can use any type when you throw (including built-in types), but often you'll use special types created just for throwing exceptions. The keyword throw causes a number of relatively magical things to happen. First it creates an object that isn't there under normal program execution, and of course the constructor is called for that object. Then the object is, in effect, returned from the function, even though that object type isn't normally what the function is designed to return. A simplistic way to think about exception handling is as an alternate return mechanism, although you get into trouble if you take the analogy too far - you can also exit from ordinary scopes by throwing an exception. But a value is returned, and the function or scope exits. Any similarity to function returns ends there because where you return to is someplace completely different than for a normal function call. Of course, the exception object itself is also properly cleaned up at the appropriate point. In addition, you can throw as many different types of objects as you want. Typically, you'll throw a different type for each different type of error. The idea is to store the information in the object and the type of object, so someone in the bigger context can figure out what to do with your exception. Catching an exception If a function throws an exception, it must assume that exception is caught and dealt with. As mentioned before, one of the advantages of C++ exception handling is that it allows you to concentrate on the problem you're actually trying to solve in one place, and then deal with the errors from that code in another place. The try block If you're inside a function and you throw an exception (or a called function throws an exception), that function will exit in the process of throwing. If you don't want a throw to leave a function, you can set up a special block within the function where you try to solve your actual programming problem (and potentially generate exceptions). This is called the try block because you try your various function calls there. With exception handling, you put everything in a try block without error checking. This means your code is a lot easier to write and easier to read because the goal of the code is not confused with the error checking. Exception handlers Of course, the thrown exception must end up someplace. This is the exception handler, and there's one for every exception type you want to catch. The handlers must appear directly after the try block. If an exception is thrown, the exception-handling mechanism goes hunting for the first handler with an argument that matches the type of the exception. Then it enters that catch clause, and the exception is considered handled. Notice that, within the try block, a number of different function calls might generate the same exception, but you only need one handler. Termination vs. In termination (which is what C++ supports) you assume the error is so critical there's no way to get back to where the exception occurred. Whoever threw the exception decided there was no way to salvage the situation, and they don't want to come back. The alternative is called resumption. It means the exception handler is expected to do something to rectify the situation, and then the faulting function is retried, presuming success the second time. Alternatively, place your try block inside a while loop that keeps reentering the try block until the result is satisfactory. Historically, programmers using operating systems that supported resumptive exception handling eventually ended up using termination-like code and skipping resumption. So although resumption sounds attractive at first, it seems it isn't quite so useful in practice. The exception specification You're not required to inform the person using your function what exceptions you might throw. However, this is considered very uncivilized because it means he cannot be sure what code to write to catch all potential exceptions. Of course, if he has your source code, he can hunt through and look for throw statements, but very often a library doesn't come with sources. C++ provides a syntax to allow you to politely tell the user what exceptions this function throws, so the user may handle them. This is the exception specification and it's part of the function declaration, appearing after the argument list. The exception specification reuses the keyword throw, followed by a parenthesized list of all the potential exception types. So your function declaration may look like void f() throw(toobig, toosmall, divzero); With exceptions, the traditional function declaration void f(); means that any type of exception may be thrown from the function. If you say void f() throw(); it means that no exceptions are thrown from a function. For good coding policy, good documentation, and ease-of-use for the function caller, you should always use an exception specification when you write a function that throws exceptions. The special function unexpected( ) is called when you throw something other than what appears in the exception specification. You do so with a function called set_unexpected( ) which, like set_new_handler( ), takes the address of a function with no arguments and void return value. Also, it returns the previous value of the unexpected( ) pointer so you can save it and restore it later. Often exception classes will be this small, but sometimes they contain additional information so that the handlers can query them. Version one of g( ), called by f( ), doesn't throw any exceptions so this is true. But then someone changes g( ) so it throws exceptions and the new g( ) is linked in with f( ). Now f( ) begins to throw a new exception, unbeknown to the creator of f( ). Thus the exception specification is violated. The my_unexpected( ) function has no arguments or return value, following the proper form for a custom unexpected( ) function. It simply prints a message so you can see it has been called, then exits the program. Your new unexpected( ) function must not return (that is, you can write the code that way but it's an error). However, it can throw another exception (you can even rethrow the same exception), or call exit( ) or abort( ). If unexpected( ) throws an exception, the search for the handler starts at the function call that threw the unexpected exception. The default value is terminate( ) (mentioned later), but whenever you use exceptions and specifications you should write your own unexpected( ) to log the error and either rethrow it, throw something new, or terminate the program. In main( ), the try block is within a for loop so all the possibilities are exercised. Note that this is a way to achieve something like resumption - nest the try block inside a for, while, do, or if and cause any exceptions to attempt to repair the problem; then attempt the try block again. Only the Up and Fit exceptions are caught because those are the only ones the programmer of f( ) said would be thrown. Version two of g( ) causes my_unexpected( ) to be called because f( ) then throws an int. Better exception specifications? You may feel the existing exception specification rules aren't very safe, and that void f(); should mean that no exceptions are thrown from this function. Unfortunately you can't always know by looking at the code in a function whether an exception will be thrown - it could happen because of a memory allocation, for example. Worse, existing functions written before exception handling was introduced may find themselves inadvertently throwing exceptions because of the functions they call (which may be linked into new, exception-throwing versions). Thus, the ambiguity, so void f(); means Maybe I'll throw an exception, maybe I won't. This ambiguity is necessary to avoid hindering code evolution. Catching any exception As mentioned, if your function has no exception specification, any type of exception can be thrown. One solution to this problem is to create a handler that catches any type of exception. The ellipses give you no possibility to have an argument or to know anything about the type of the exception. It's a catch-all. Rethrowing an exception Sometimes you'll want to rethrow the exception that you just caught, particularly when you use the ellipses to catch any exception because there's no information available about the exception. In addition, everything about the exception object is preserved, so the handler at the higher context that catches the specific exception type is able to extract all the information from that object. At that point, the exception is considered caught, and no further searching occurs. Like unexpected( ), terminate is actually a pointer to a function. Its default value is the Standard C library function abort( ), which immediately exits the program with no calls to the normal termination functions (which means that destructors for global and static objects might not be called). No cleanups occur for an uncaught exception; that is, no destructors are called. If you don't wrap your code (including, if necessary, all the code in main( )) in a try block followed by handlers and ending with a default handler (catch(...)) to catch all exceptions, then you will take your lumps. An uncaught exception should be thought of as a programming error. Your custom terminate( ) must take no arguments and have a void return value. In addition, any terminate( ) handler you install must not return or throw an exception, but instead must call some sort of program-termination function. If terminate( ) is called, it means the problem is unrecoverable. Like unexpected( ), the terminate( ) function pointer should never be null. Here's an example showing the use of set_terminate( ). Even though you may be familiar with seeing a semicolon right after a pointer-to-function definition, it's just another kind of variable and may be initialized when it is defined. The class Botch not only throws an exception inside f( ), but also in its destructor. This is one of the situations that causes a call to terminate( ), as you can see in main( ). Thus, a destructor that throws an exception or causes one to be thrown is a design error. Cleaning up Part of the magic of exception handling is that you can pop from normal program flow into the appropriate exception handler. This wouldn't be very useful, however, if things weren't cleaned up properly as the exception was thrown. C++ exception handling guarantees that as you leave a scope, all objects in that scope whose constructors have been completed will have destructors called. Here's an example that demonstrates that constructors that aren't completed don't have the associated destructors called. It keeps a count of the number of objects created with a static data member i, and the number of the particular object with objnum, and a character buffer called name to hold an identifier. This buffer is first set to zeroes. Then the constructor argument is copied in. There are two cases where a throw can occur in the constructor. The first case happens if this is the fifth object created (not a real exception condition, but demonstrates an exception thrown during array construction). The type thrown is int, which is the type promised in the exception specification. The second case, also contrived, happens if the first character of the argument string is 'z', in which case a char is thrown. Because char is not listed in the exception specification, this will cause a call to unexpected( ). The array versions of new and delete are overloaded for the class, so you can see when they're called. The function unexpected_rethrow( ) prints a message and rethrows the same exception. It is installed as the unexpected( ) function in the first line of main( ). Then some objects of type Noisy are created in a try block, but the array causes an exception to be thrown, so the object n2 is never created. Because the fifth constructor never completes, only the destructors for objects 1-4 are called. The storage for the array is allocated separately with a single call to the global new. Notice that even though delete is never explicitly called anywhere in the program, the exception-handling system knows it must call delete to properly release the storage. This behavior happens only with normal versions of operator new. If you use the placement syntax described in Chapter 11, the exception-handling mechanism will not call delete for that object because then it might release memory that was not allocated on the heap. Finally, object n1 is destroyed, but not object n2 because it was never created. In the section testing unexpected_rethrow( ), the n3 object is created, and the constructor of n4 is begun. But before it can complete, an exception is thrown. This exception is of type char, which violates the exception specification, so the unexpected( ) function is called (which is unexpected_rethrow( ), in this case). This rethrows the same exception, which is expected this time, because unexpected_rethrow( ) can throw any type of exception. The search begins right after the constructor for n4, and the char exception handler catches it (after destroying n3, the only successfully created object). Thus, the effect of unexpected_rethrow( ) is to take any unexpected exception and make it expected; used this way it provides a filter to allow you to track the appearance of unexpected exceptions and pass them through. This means you must be especially diligent while writing your constructor. The general difficulty is allocating resources in constructors. If an exception occurs in the constructor, the destructor doesn't get a chance to deallocate the resource. This problem occurs most often with naked pointers. However, inside Dog::operator new, an exception is thrown (as an example of an out-of-memory error). Suddenly, you end up inside the handler, without the UseResources destructor being called. This is correct because the UseResources constructor was unable to finish, but it means the Cat object that was successfully created on the heap is never destroyed. Making everything an object To prevent this, guard against these raw resource allocations by placing the allocations inside their own objects with their own constructors and destructors. This way, each allocation becomes atomic, as an object, and if it fails, the other resource allocation objects are properly cleaned up. Yours may have other arguments. The constructors for these objects are called before the body of the UseResources constructor, and any of these constructors that complete before an exception is thrown will have their associated destructors called. In this example, RangeError is very simple and assumes all the necessary information is in the class name, but you may also want to add a member that contains the value of the index, if that is useful. Exception matching When an exception is thrown, the exception-handling system looks through the nearest handlers in the order they are written. When it finds a match, the exception is considered handled, and no further searching occurs. Matching an exception doesn't require a perfect match between the exception and its handler. An object or reference to a derived-class object will match a handler for the base class. However, no automatic type conversions are used to convert one exception type to another in the process of matching. That means the second and third handlers are never called because the first one captures them all. It makes more sense to catch the derived types first and put the base type at the end to catch anything less specific (or a derived class introduced later in the development cycle). In addition, if Small and Big represent larger objects than the base class Trouble (which is often true because you regularly add data members to derived classes), then those objects are sliced to fit into the first handler. Of course, in this example it isn't important because there are no additional members in the derived classes and there are no argument identifiers in the handlers anyway. You'll usually want to use reference arguments rather than objects in your handlers to avoid slicing off information. Standard exceptions The set of exceptions used with the Standard C++ library are also available for your own use. Generally it's easier and faster to start with a standard exception class than to try to define your own. If the standard class doesn't do what you need, you can derive from it. The following tables describe the standard exceptions: exception The base class for all the exceptions thrown by the C++ standard library. You can ask what( ) and get a result that can be displayed as a character representation. Reports program logic errors, which could presumably be detected before the program executes. Reports run-time errors, which can presumably be detected only when the program executes. The iostream exception class ios::failure is also derived from exception, but it has no further subclasses. The classes in both of the following tables can be used as they are, or they can act as base classes to derive your own more specific types of exceptions. Exception classes derived from logic_error domain_error Reports violations of a precondition. Exception classes derived from runtime_error range_error Reports violation of a postcondition. Programming with exceptions For most programmers, especially C programmers, exceptions are not available in their existing language and take a bit of adjustment. Here are some guidelines for programming with exceptions. When to avoid exceptions Exceptions aren't the answer to all problems. In fact, if you simply go looking for something to pound with your new hammer, you'll cause trouble. The following sections point out situations where exceptions are not warranted. Not for asynchronous events The Standard C signal( ) system, and any similar system, handles asynchronous events: events that happen outside the scope of the program, and thus events the program cannot anticipate. C++ exceptions cannot be used to handle asynchronous events because the exception and its handler are on the same call stack. That is, exceptions rely on scoping, whereas asynchronous events must be handled by completely separate code that is not part of the normal program flow (typically, interrupt service routines or event loops). This is not to say that asynchronous events cannot be associated with exceptions. But the interrupt handler should do its job as quickly as possible and then return. Later, at some well-defined point in the program, an exception might be thrown based on the interrupt. Not for ordinary error conditions If you have enough information to handle an error, it's not an exception. You should take care of it in the current context rather than throwing an exception to a larger context. Also, C++ exceptions are not thrown for machine-level events like divide-by-zero. It's assumed these are dealt with by some other mechanism, like the operating system or hardware. That way, C++ exceptions can be reasonably efficient, and their use is isolated to program-level exceptional conditions. Not for flow-of-control An exception looks somewhat like an alternate return mechanism and somewhat like a switch statement, so you can be tempted to use them for other than their original intent. This is a bad idea, partly because the exception-handling system is significantly less efficient than normal program execution; exceptions are a rare event, so the normal program shouldn't pay for them. Also, exceptions from anything other than error conditions are quite confusing to the user of your class or function. You're not forced to use exceptions Some programs are quite simple, many utilities, for example. You may only need to take input and perform some processing. In these programs you might attempt to allocate memory and fail, or try to open a file and fail, and so on. It is acceptable in these programs to use assert( ) or to print a message and abort( ) the program, allowing the system to clean up the mess, rather than to work very hard to catch all exceptions and recover all the resources yourself. Basically, if you don't need to use exceptions, you don't have to. New exceptions, old code Another situation that arises is the modification of an existing program that doesn't use exceptions. You may introduce a library that does use exceptions and wonder if you need to modify all your code throughout the program. You can refine this to whatever degree necessary by adding more specific handlers, but, in any case, the code you're forced to add can be minimal. You can also isolate your exception-generating code in a try block and write handlers to convert the exceptions into your existing error-handling scheme. It's truly important to think about exceptions when you're creating a library for someone else to use, and you can't know how they need to respond to critical error conditions. Typical uses of exceptions Do use exceptions to 9. Fix the problem and call the function (which caused the exception) again. Patch things up and continue without retrying the function. Calculate some alternative result instead of what the function was supposed to produce. Do whatever you can in the current context and rethrow the same exception to a higher context. Do whatever you can in the current context and throw a different exception to a higher context. Terminate the program. Wrap functions (especially C library functions) that use ordinary error schemes so they produce exceptions instead. Simplify. If your exception scheme makes things more complicated, then it is painful and annoying to use. Make your library and program safer. This is a short-term investment (for debugging) and a long-term investment (for application robustness). Always use exception specifications The exception specification is like a function prototype: It tells the user to write exception-handling code and what exceptions to handle. It tells the compiler the exceptions that may come out of this function. Of course, you can't always anticipate by looking at the code what exceptions will arise from a particular function. Sometimes the functions it calls produce an unexpected exception, and sometimes an old function that didn't throw an exception is replaced with a new one that does, and you'll get a call to unexpected( ). Anytime you use exception specifications or call functions that do, you should create your own unexpected( ) function that logs a message and rethrows the same exception. Start with standard exceptions Check out the Standard C++ library exceptions before creating your own. If a standard exception does what you need, chances are it's a lot easier for your user to understand and handle. If the exception type you want isn't part of the standard library, try to derive one from an existing standard exception. It's nice for your users if they can always write their code to expect the what( ) function defined in the exception( ) class interface. Nest your own exceptions If you create exceptions for your particular class, it's a very good idea to nest the exception classes inside your class to provide a clear message to the reader that this exception is used only for your class. In addition, it prevents the pollution of the namespace. You can nest your exceptions even if you're deriving them from C++ standard exceptions. Use exception hierarchies Exception hierarchies provide a valuable way to classify the different types of critical errors that may be encountered with your class or library. This gives helpful information to users, assists them in organizing their code, and gives them the option of ignoring all the specific types of exceptions and just catching the base-class type. Also, any exceptions added later by inheriting from the same base class will not force all existing code to be rewritten - the base-class handler will catch the new exception. Of course, the Standard C++ exceptions are a good example of an exception hierarchy, and one that you can use to build upon. It turns out that exception hierarchies are a useful place for multiple inheritance because a base-class handler from any of the roots of the multiply inherited exception class can handle the exception. Chances are this is not what you want because the object will behave like a base-class object and not the derived class object it really is (or rather, was - before it was sliced). Although you can also throw and catch pointers, by doing so you introduce more coupling - the thrower and the catcher must agree on how the exception object is allocated and cleaned up. This is a problem because the exception itself may have occurred from heap exhaustion. If you throw exception objects, the exception-handling system takes care of all storage. Throw exceptions in constructors Because a constructor has no return value, you've previously had two choices to report an error during construction: 18. Set a nonlocal flag and hope the user checks it. Return an incompletely created object and hope the user checks it. This is a serious problem because C programmers have come to rely on an implied guarantee that object creation is always successful, which is not unreasonable in C where types are so primitive. But continuing execution after construction fails in a C++ program is a guaranteed disaster, so constructors are one of the most important places to throw exceptions - now you have a safe, effective way to handle constructor errors. However, you must also pay attention to pointers inside objects and the way cleanup occurs when an exception is thrown inside a constructor. If this happens, it means that a new exception may be thrown before the catch-clause for an existing exception is reached, which will cause a call to terminate( ). This means that if you call any functions inside a destructor that may throw exceptions, those calls should be within a try block in the destructor, and the destructor must handle all exceptions itself. None must escape from the destructor. Avoid naked pointers See WRAPPED.CPP (page Error! Bookmark not defined.). A naked pointer usually means vulnerability in the constructor if resources are allocated for that pointer. A pointer doesn't have a destructor, so those resources won't be released if an exception is thrown in the constructor. Overhead Of course it costs something for this new feature; when an exception is thrown there's considerable run-time overhead. This is the reason you never want to use exceptions as part of your normal flow-of-control, no matter how tempting and clever it may seem. Exceptions should occur only rarely, so the overhead is piled on the exception and not on the normally executing code. Whether or not this is actually true depends on the particular compiler implementation you're using. Exception handling also causes extra information to be put on the stack by the compiler, to aid in stack unwinding. Exception objects are properly passed around like any other objects, except that they can be passed into and out of what can be thought of as a special exception scope (which may just be the global scope). That's how they go from one place to another. When the exception handler is finished, the exception objects are properly destroyed. Summary Error recovery is a fundamental concern for every program you write, and it's especially important in C++, where one of the goals is to create program components for others to use. To create a robust system, each component must be robust. The goals for exception handling in C++ are to simplify the creation of large, reliable programs using less code than currently possible, with more confidence that your application doesn't have an unhandled error. This is accomplished with little or no performance penalty, and with low impact on existing code. Basic exceptions are not terribly difficult to learn, and you should begin using them in your programs as soon as you can. Exceptions are one of those features that provide immediate and significant benefits to your project. Exercises 1. Create a class with member functions that throw exceptions. Within this class, make a nested class to use as an exception object. It takes a single char* as its argument; this represents a description string. Create a member function that throws this exception. 4. Create a class with its own operator new. This operator should allocate 10 objects, and on the 11th run out of memory and throw an exception. Also add a static member function that reclaims this memory. Now create a main( ) with a try block and a catch clause that calls the memory-restoration routine. Put these inside a while loop, to demonstrate recovering from an exception and continuing execution. 5. Create a destructor that throws an exception, and write code to prove to yourself that this is a bad idea by showing that if a new exception is thrown before the handler for the existing one is reached, terminate( ) is called. 6. Prove to yourself that all exception objects (the ones that are thrown) are properly destroyed. 7. Prove to yourself that if you create an exception object on the heap and throw the pointer to that object, it will not be cleaned up. 8. (Advanced). Set up an interesting situation, throw an object of your new type, and analyze the result. 24: Run-time type identification Run-time type identification (RTTI) lets you find the exact type of an object when you have only a pointer or reference to the base type. This can be thought of as a secondary feature in C++, a pragmatism to help out when you get into messy situations. Normally, you'll want to intentionally ignore the exact type of an object and let the virtual function mechanism implement the correct behavior for that type. But occasionally it's useful to know the exact type of an object for which you only have a base pointer. Often this information allows you to perform a special-case operation more efficiently or prevent a base-class interface from becoming ungainly. It happens enough that most class libraries contain virtual functions to produce run-time type information. When exception handling was added to C++, it required the exact type information about objects. It became an easy next step to build access to that information into the language. This chapter explains what RTTI is for and how to use it. In addition, it explains the why and how of the new C++ cast syntax, which has the same appearance as RTTI. The Shape example This is an example of a class hierarchy that uses polymorphism. The generic type is the base class Shape, and the specific derived types are Circle, Square, and Triangle: This is a typical class-hierarchy diagram, with the base class at the top and the derived classes growing downward. In this example, the virtual function in the Shape interface is draw( ), so the intent is for the client programmer to call draw( ) through a generic Shape pointer. Thus, you generally create a specific object (Circle, Square, or Triangle), take its address and cast it to a Shape* (forgetting the specific type of the object), and use that anonymous pointer in the rest of the program. Historically, diagrams are drawn as seen above, so the act of casting from a more derived type to a base type is called upcasting. What is RTTI? But what if you have a special programming problem that's easiest to solve if you know the exact type of a generic pointer? For example, suppose you want to allow your users to highlight all the shapes of any particular type by turning them purple. This way, they can find all the triangles on the screen by highlighting them. Your natural first approach may be to try a virtual function like TurnColorIfYouAreA( ), which allows enumerated arguments of some type color and of Shape::Circle, Shape::Square, or Shape::Triangle. To solve this sort of problem, most class library designers put virtual functions in the base class to return type information about the specific object at run-time. You may have seen library member functions with names like isA( ) and typeOf( ). These are vendor-defined RTTI functions. This meant that with a small language extension the programmer could also get the run-time type information about an object. All library vendors were adding their own RTTI anyway, so it was included in the language. RTTI, like exceptions, depends on type information residing in the virtual function table. If you try to use RTTI on a class that has no virtual functions, you'll get unexpected results. Two syntaxes for RTTI There are two different ways to use RTTI. The first acts like sizeof( ) because it looks like a function, but it's actually implemented by the compiler. These can be compared to each other with the operator== and operator!=, and you can also ask for the name( ) of the type, which returns a string representation of the type name. Note that if you hand typeid( ) a Shape*, it will say that the type is Shape*, so if you want to know the exact type it is pointing to, you must dereference the pointer. You can also ask a typeinfo object if it precedes another typeinfo object in the implementation-defined collation sequence, using before(typeinfoand), which returns true or false. The second syntax for RTTI is called a type-safe downcast. The reason for the term downcast is (again) the historical arrangement of the class hierarchy diagram. If casting a Circle* to a Shape* is an upcast, then casting a Shape* to a Circle* is a downcast. A common approach in vendor-defined RTTI is to create some function that attempts to assign (for this example) a Shape* to a Circle*, checking the type in the process. The function argument is what you are trying to cast from. Normally you might be hunting for one type (triangles to turn purple, for instance), but the following example fragment can be used if you want to count the number of various shapes. You would do something like that if you had control of the source code for the class and could change it. In addition, the syntax for RTTI may then be different from one class to another. Syntax specifics This section looks at the details of how the two forms of RTTI work, and how they differ. So the following expressions are true: typeid(47) == typeid(int) typeid(0) == typeid(int) int i; typeid(i) == typeid(int) typeid(andi) == typeid(int*) Producing the proper type name typeid( ) must work properly in all situations. Nonpolymorphic types Although typeid( ) works with nonpolymorphic types (those that don't have a virtual function in the base class), the information you get this way is dubious. Because there's no polymorphism, the static type information is used: typeid(*xp) == typeid(X) typeid(*xp)!= typeid(Y) RTTI is intended for use only with polymorphic classes. Casting to intermediate levels dynamic_cast can detect both exact types and, in an inheritance hierarchy with multiple levels, intermediate types. If you create an mi2 and upcast it to the root (in this case, one of the two possible roots is chosen), then the dynamic_cast back to either of the derived levels MI or mi2 is successful. Casting to intermediate levels brings up an interesting difference between dynamic_cast and typeid( ). Thus it doesn't give you intermediate-level information. RTTI provides a convenient way to do this. The template uses a constant int to differentiate one class from another, but class arguments will work as well. Inside both the constructor and destructor, RTTI information is used to produce the name of the class to print. The class X uses both inheritance and composition to create a class that has an interesting order of constructor and destructor calls. This technique is often useful in situations when you're trying to understand how the language works. References RTTI must adjust somewhat to work with references. The contrast between pointers and references occurs because a reference is always dereferenced for you by the compiler, whereas a pointer's type or the type it points to may be examined. But what happens if the cast fails? If an exception was not thrown here, then xr would be unbound, and the guarantee that all objects or references are constructed storage would be broken. An exception is also thrown if you try to dereference a null pointer in the process of calling typeid( ). The dynamic_cast also works correctly. Sensible uses for RTTI Because it allows you to discover type information from an anonymous polymorphic pointer, RTTI is ripe for misuse by the novice because RTTI may make sense before virtual functions do. For many people coming from a procedural background, it's very difficult not to organize their programs into sets of switch statements. They could accomplish this with RTTI and thus lose the very important value of polymorphism in code development and maintenance. The intent of C++ is that you use virtual functions throughout your code, and you only use RTTI when you must. If the base class comes from a library or is otherwise controlled by someone else, a solution to the problem is RTTI: You can inherit a new type and add your extra member function. Elsewhere in the code you can detect your particular type and call that member function. This doesn't destroy the polymorphism and extensibility of the program, because adding a new type will not require you to hunt for switch statements. However, when you add new code in your main body that requires your new feature, you'll have to detect your particular type. Putting a feature in a base class might mean that, for the benefit of one particular class, all the other classes derived from that base require some meaningless stub of a virtual function. This makes the interface less clear and annoys those who must redefine pure virtual functions when they derive from that base class. For example, suppose that in the WIND5.CPP program in Chapter 13 (page Error! Bookmark not defined.) you wanted to clear the spit valves of all the instruments in your orchestra that had them. One option is to put a virtual ClearSpitValve( ) function in the base class Instrument, but this is confusing because it implies that Percussion and electronic instruments also have spit valves. RTTI provides a much more reasonable solution in this case because you can place the function in the specific class (Wind in this case) where it's appropriate. Finally, RTTI will sometimes solve efficiency problems. But later, the specific type information must be recovered to properly sort the trash, and so RTTI is used. In Chapter 14, an RTTI system was inserted into the class hierarchy, but as you can see here, it's more convenient to use C++'s built-in RTTI. Mechanism and overhead of RTTI Typically, RTTI is implemented by placing an additional pointer in the VTABLE. This pointer points to the typeinfo structure for that particular type. Also, this is a deterministic process - you always know how long it's going to take. The situation is (of course) more complicated with multiple inheritance where a base type may appear more than once in an inheritance hierarchy and where virtual base classes are used. In addition, dynamic_cast allows you to compare any type to any other type; you aren't restricted to comparing types within the same hierarchy. This adds extra overhead to the library routine used by dynamic_cast. Creating your own RTTI If your compiler doesn't yet support RTTI, you can build it into your class libraries quite easily. The following uses a static member function called dynacast( ) that calls a type information function dynamic_type( ). In the classes derived from Security, you can see that each defines its own typeID enumeration by adding to baseID. It's essential that baseID be directly accessible in the derived class because the enum must be evaluated at compile-time, so the usual approach of reading private data with an inline function would fail. This is a good example of the need for the protected mechanism. The enum baseID establishes a base identifier for all types derived from Security. That way, if an identifier clash ever occurs, you can change all the identifiers by changing the base value. In all the classes, the class identifier number is protected, so it's directly available to derived classes but not to the end user. This example illustrates what built-in RTTI must cope with. Not only must you be able to determine the exact type, you must also be able to find out whether your exact type is derived from the type you're looking for. For example, Metal is derived from Commodity, which has a function called special( ), so if you have a Metal object you can call special( ) for it. If dynamic_type( ) told you only the exact type of the object, you could ask it if a Metal were a Commodity, and it would say no, which is untrue. Therefore, the system must be set up so it will properly cast to intermediate types in a hierarchy as well as exact types. The dynacast( ) function determines the type information by calling the virtual dynamic_type( ) function for the Security pointer it's passed. This function takes an argument of the typeID for the class you're trying to cast to. It's a virtual function, so the function body is the one for the exact type of the object. Each dynamic_type( ) function first checks to see if the identifier it was passed is an exact match for its own type. If that isn't true, it must check to see if it matches a base type; this is accomplished by making a call to the base class dynamic_type( ). Just like a recursive function call, each dynamic_type( ) checks against its own identifier. If it doesn't find a match, it returns the result of calling the base class dynamic_type( ). When the root of the hierarchy is reached, zero is returned to indicate no match was found. If dynamic_type( ) returns one (for true) the object pointed to is either the exact type you're asking about or derived from that type, and dynacast( ) takes the Security pointer and casts it to the desired type. If the return value is false, dynacast( ) returns zero to indicate the cast was unsuccessful. In this way it works just like the C++ dynamic_cast operator. The C++ dynamic_cast operator does one more thing the above scheme can't do: It compares types from one inheritance hierarchy to another, completely separate inheritance hierarchy. This adds generality to the system for those unusual cases where you want to compare across hierarchies, but it also adds some complexity and overhead. You can easily imagine how to create a DYNAMIC_CAST macro that uses the above scheme and allows an easier transition to the built-in dynamic_cast operator. New cast syntax Whenever you use a cast, you're breaking the type system. 67 You're telling the compiler that even though you know an object is a certain type, you're going to pretend it is a different type. This is an inherently dangerous activity, and a clear source of errors. Unfortunately, each cast is different: the name of the pretender type surrounded by parentheses. So if you are given a piece of code that isn't working correctly and you know you want to examine all casts to see if they're the source of the errors, how can you guarantee that you find all the casts? In a C program, you can't. To solve this problem, C++ provides a consistent casting syntax using four reserved words: dynamic_cast (the subject of the first part of this chapter), const_cast, static_cast, and reinterpret_cast. This window of opportunity opened up when the need for dynamic_cast arose - the meaning of the existing cast syntax was already far too overloaded to support any additional functionality. By using these casts instead of the (newtype) syntax, you can easily search for all the casts in any program. But if you turn on full errors for the new cast syntax, you can be guaranteed that you'll find all the places in your project where casts occur, which will make bug-hunting much easier. The following table describes the different forms of casting: static_cast For well-behaved and reasonably well-behaved casts, including things you might now do without a cast (e.g., an upcast or automatic type conversion). The key is that you'll need to cast back to the original type to use it safely. The type you cast to is typically used only for bit twiddling or some other mysterious purpose. This is the most dangerous of all the casts. The three new casts will be described more completely in the following sections. These include safe conversions that the compiler would allow you to do without a cast and less-safe conversions that are nonetheless well-defined. Promoting from an int to a long or float is not a problem because the latter can always hold every value that an int can contain. Although it's unnecessary, you can use static_cast to highlight these promotions. Converting back the other way is shown in (2). Here, you can lose data because an int is not as wide as a long or a float - it won't hold numbers of the same size. Thus these are called narrowing conversions. The compiler will still perform these, but will often give you a warning. You can eliminate this warning and indicate that you really did mean it using a cast. Assigning from a void* is not allowed without a cast in C++ (unlike C), as seen in (3). This is dangerous and requires that a programmer know what he's doing. The static_cast, at least, is easier to locate than the old standard cast when you're hunting for bugs. Section (4) shows the kinds of implicit type conversions that are normally performed automatically by the compiler. These are automatic and require no casting, but again static_cast highlights the action in case you want to make it clear what's happening or hunt for it later. If a class hierarchy has no virtual functions or if you have other information that allows you to safely downcast, it's slightly faster to do the downcast statically than with dynamic_cast, as shown in (5). In addition, static_cast won't allow you to cast out of the hierarchy, as the traditional cast will, so it's safer. However, statically navigating class hierarchies is always risky and you should use dynamic_cast unless you have a special situation. This is the only conversion allowed with const_cast; if any other conversion is involved it must be done separately or you'll get a compile-time error. The old-style cast will accomplish this, but the const_cast is the appropriate one to use. The same holds true for volatile. If you want to change a class member inside a const member function, the traditional approach is to cast away constness by saying (X*)this. At the very least, your compiler should contain switches to allow you to force the use of const_cast and reinterpret_cast, which will locate the most unsafe of the casts. A reinterpret_cast pretends that an object is just a bit pattern that can be treated (for some dark purpose) as if it were an entirely different type of object. This is the low-level bit twiddling that C is notorious for. You'll virtually always need to reinterpret_cast back to the original type before doing anything else with it. In main( ), an X object is printed out to show that it gets initialized to zero, and then its address is cast to an int* using a reinterpret_cast. Pretending it's an int*, the object is indexed into as if it were an array and (in theory) element one is set to 47. But here's the output:68 0 0 0 0 0 47 0 0 0 0 Clearly, it's not safe to assume that the data in the object begins at the starting address of the object. To fix the problem, the size of the VPTR is calculated by subtracting the size of the data members from the size of the object. Then the address of the object is cast (again, with reinterpret_cast) to a long, and the starting address of the actual data is established, assuming the VPTR is placed at the beginning of the object. The resulting number is cast back to an int* and the indexing now produces the desired result: 0 47 0 0 0 Of course, this is inadvisable and nonportable programming. That's the kind of thing that a reinterpret_cast indicates, but it's available when you decide you have to use it. Summary RTTI is a convenient extra feature, a bit of icing on the cake. Because some form of virtual-function-based RTTI has appeared in almost all class libraries, this is a useful feature because it means 1. You don't have to build it into your own libraries. 2. You don't have to worry whether it will be built into someone else's library. 3. You don't have the extra programming overhead of maintaining an RTTI scheme during inheritance. 4. The syntax is consistent, so you don't have to figure out a new one for each library. While RTTI is a convenience, like most features in C++ it can be misused by either a naive or determined programmer. The most common misuse may come from the programmer who doesn't understand virtual functions and uses RTTI to do type-check coding instead. The philosophy of C++ seems to be to provide you with powerful tools and guard for type violations and integrity, but if you want to deliberately misuse or get around a language feature, there's nothing to stop you. Sometimes a slight burn is the fastest way to gain experience. The new cast syntax will be a big help during debugging because casting opens a hole into your type system and allows errors to slip in. The new cast syntax will allow you to more easily locate these error entryways. Exercises 1. Use RTTI to assist in program debugging by printing out the exact name of a template using typeid( ). Instantiate the template for various types and see what the results are. 2. Implement the function TurnColorIfYouAreA( ) described earlier in this chapter using RTTI. 3. Modify the Instrument hierarchy from Chapter 13 by first copying WIND5.CPP to a new location. Now add a virtual ClearSpitValve( ) function to the Wind class, and redefine it for all the classes inherited from Wind. Instantiate a TStash to hold Instrument pointers and fill it up with various types of Instrument objects created using new. Now use RTTI to move through the container looking for objects in class Wind, or derived from Wind. Call the ClearSpitValve( ) function for these objects. Notice that it would unpleasantly confuse the Instrument base class if it contained a ClearSpitValve( ) function. In this chapter, the basic concepts of design patterns will be introduced along with several examples. This should whet your appetite to read Design Patterns (a source of what has now become an essential, almost mandatory, vocabulary for OOP programmers). The latter part of this chapter contains an example of the design evolution process, starting with an initial solution and moving through the logic and process of evolving the solution to more appropriate designs. The pattern concept Initially, you can think of a pattern as an especially clever and insightful way of solving a particular class of problems. That is, it looks like a lot of people have worked out all the angles of a problem and have come up with the most general, flexible solution for it. The problem could be one you have seen and solved before, but your solution probably didn't have the kind of completeness you'll see embodied in a pattern. Although they're called design patterns, they really aren't tied to the realm of design. A pattern seems to stand apart from the traditional way of thinking about analysis, design, and implementation. Instead, a pattern embodies a complete idea within a program, and thus it can sometimes appear at the analysis phase or high-level design phase. The basic concept of a pattern can also be seen as the basic concept of program design: adding a layer of abstraction. Whenever you abstract something you're isolating particular details, and one of the most compelling motivations behind this is to separate things that change from things that stay the same. Another way to put this is that once you find some part of your program that's likely to change for one reason or another, you'll want to keep those changes from propagating other changes throughout your code. Not only does this make the code much cheaper to maintain, but it also turns out that it is usually simpler to understand (which results in lowered costs). Once you discover the vector of change, you have the focal point around which to structure your design. So the goal of design patterns is to isolate changes in your code. If you look at it this way, you've been seeing some design patterns already in this book. For example, inheritance can be thought of as a design pattern (albeit one implemented by the compiler). It allows you to express differences in behavior (that's the thing that changes) in objects that all have the same interface (that's what stays the same). Composition can also be considered a pattern, since it allows you to change - dynamically or statically - the objects that implement your class, and thus the way that class works. You've also already seen another pattern that appears in Design Patterns: the iterator (Java 1.0 and 1.1 capriciously calls it the Enumeration; Java 1.2 collections use iterator). This hides the particular implementation of the collection as you're stepping through and selecting the elements one by one. The iterator allows you to write generic code that performs an operation on all of the elements in a sequence without regard to the way that sequence is built. Thus your generic code can be used with any collection that can produce an iterator. The singleton Possibly the simplest design pattern is the singleton, which is a way to provide one and only one instance of an object. You must make all constructors private, and you must create at least one constructor to prevent the compiler from synthesizing a default constructor for you (which it will create as friendly). At this point, you decide how you're going to create your object. Here, it's created statically, but you can also wait until the client programmer asks for one and create it on demand. In any case, the object should be stored privately. You provide access through public methods. Here, getHandle( ) produces the handle to the Singleton object. The rest of the interface (getValue( ) and setValue( )) is the regular class interface. Java also allows the creation of objects through cloning. In this example, making the class final prevents cloning. Since Singleton is inherited directly from Object, the clone( ) member function remains protected so it cannot be used (doing so produces a compile-time error). This is also a technique to create a limited pool of objects. In that situation, however, you can be confronted with the problem of sharing objects in the pool. If this is an issue, you can create a solution involving a check-out and check-in of the shared objects. Classifying patterns The Design Patterns book discusses 23 different patterns, classified under three purposes (all of which revolve around the particular aspect that can vary). The three purposes are: 1. Creational: how an object can be created. This often involves isolating the details of object creation so your code isn't dependent on what types of objects there are and thus doesn't have to be changed when you add a new type of object. The aforementioned Singleton is classified as a creational pattern, and later in this chapter you'll see examples of Factory Method and Prototype. 2. Structural: designing objects to satisfy particular project constraints. These work with the way objects are connected with other objects to ensure that changes in the system don't require changes to those connections. 3. Behavioral: objects that handle particular types of actions within a program. These encapsulate processes that you want to perform, such as interpreting a language, fulfilling a request, moving through a sequence (as in an iterator), or implementing an algorithm. This chapter contains examples of the Observer and the Visitor patterns. The Design Patterns book has a section on each of its 23 patterns along with one or more examples for each, typically in C++ but sometimes in Smalltalk. Instead, this chapter will give some examples that should provide you with a decent feel for what patterns are about and why they are so important. The observer pattern The observer pattern solves a fairly common problem: What if a group of objects needs to update themselves when some object changes state? When you change the data, the two views must know to update themselves, and that's what the observer facilitates. It's a common enough problem that its solution has been made a part of the standard java.util library. There are two types of objects used to implement the observer pattern in Java. The Observable class keeps track of everybody who wants to be informed when a change happens, whether the state has changed or not. When someone says OK, everybody should check and potentially update themselves, the Observable class performs this task by calling the notifyObservers( ) member function for each one on the list. The notifyObservers( ) member function is part of the base class Observable. There are actually two things that change in the observer pattern: the quantity of observing objects and the way an update occurs. That is, the observer pattern allows you to modify both of these without affecting the surrounding code. The following example is similar to the ColorBoxes example from Chapter 14. Boxes are placed in a grid on the screen and each one is initialized to a random color. In addition, each box implements the Observer interface and is registered with an Observable object. When you click on a box, all of the other boxes are notified that a change has been made because the Observable object automatically calls each Observer object's update( ) member function. Inside this member function, the box checks to see if it's adjacent to the one that was clicked, and if so it changes its color to match the clicked box. But this doesn't work; try it - inside BoxObserver, create an Observable object instead of a BoxObservable object and see what happens: nothing. To get an effect, you must inherit from Observable and somewhere in your derived-class code call setChanged( ). This is the member function that sets the changed flag, which means that when you call notifyObservers( ) all of the observers will, in fact, get notified. In the example above setChanged( ) is simply called within notifyObservers( ), but you could use any criterion you want to decide when to call setChanged( ). BoxObserver contains a single Observable object called notifier, and every time an OCBox object is created, it is tied to notifier. Using a combination of code in notifyObservers( ) and update( ) you can work out some fairly complex schemes. It might appear that the way the observers are notified must be frozen at compile time in the notifyObservers( ) member function. This means that you could inherit other Observable classes and swap them at run-time if you want to change notification behavior then. The composite Simulating the trash recycler The nature of this problem is that the trash is thrown unclassified into a single bin, so the specific type information is lost. But later, the specific type information must be recovered to properly sort the trash. In the initial solution, RTTI (described in Chapter 11) is used. This is not a trivial design because it has an added constraint. That's what makes it interesting - it's more like the messy problems you're likely to encounter in your work. The extra constraint is that the trash arrives at the trash recycling plant all mixed together. The program must model the sorting of that trash. This is where RTTI comes in: you have a bunch of anonymous pieces of trash, and the program figures out exactly what type they are. The unpacking tool in Chapter 17 takes care of placing it into the correct subdirectory. The reason for doing this is that this chapter rewrites this particular example a number of times and by putting each version in its own package the class names will not clash. Several Vector objects are created to hold Trash handles. Of course, Vectors actually hold Objects so they'll hold anything at all. The reason they hold Trash (or something derived from Trash) is only because you've been careful to not put in anything except Trash. If you do put something wrong into the Vector, you won't get any compile-time warnings or errors - you'll find out only via an exception at run-time. When the Trash handles are added, they lose their specific identities and become simply Object handles (they are upcast). However, because of polymorphism the proper behavior still occurs when the dynamically-bound methods are called through the Enumeration sorter, once the resulting Object has been cast back to Trash. It looks silly to upcast the types of Trash into a collection holding base type handles, and then turn around and downcast. Why not just put the trash into the appropriate receptacle in the first place? In this program it would be easy to repair, but sometimes a system's structure and flexibility can benefit greatly from downcasting. The program satisfies the design requirements: it works. This might be fine as long as it's a one-shot solution. The key to the misuse of RTTI here is that every type is tested. If you're looking for only a subset of types because that subset needs special treatment, that's probably fine. But if you're hunting for every type inside a switch statement, then you're probably missing an important point, and definitely making your code less maintainable. In the next section we'll look at how this program evolved over several stages to become much more flexible. This should prove a valuable example in program design. Improving the design The solutions in Design Patterns are organized around the question What will change as this program evolves? This is usually the most important question that you can ask about any design. This is the promise of object-oriented programming, but it doesn't happen automatically; it requires thought and insight on your part. In this section we'll see how this process can happen during the refinement of a system. The answer to the question What will change? for the recycling system is a common one: more types will be added to the system. The goal of the design, then, is to make this addition of types as painless as possible. In the recycling program, we'd like to encapsulate all places where specific type information is mentioned, so (if for no other reason) any changes can be localized to those encapsulations. It turns out that this process also cleans up the rest of the code considerably. Often the side effect of cleaning up the code will be a system that has better structure and is more flexible. If new types are commonly added, a better solution is a single member function that takes all of the necessary information and produces a handle to an object of the correct type, already upcast to a trash object. In Design Patterns this is broadly referred to as a creational pattern (of which there are several). The specific pattern that will be applied here is a variant of the Factory Method. Here, the factory method is a static member of Trash, but more commonly it is a member function that is overridden in the derived class. The idea of the factory member function is that you pass it the essential information it needs to know to create your object, then stand back and wait for the handle (already upcast to the base type) to pop out as the return value. From then on, you treat the object polymorphically. Thus, you never even need to know the exact type of object that's created. In fact, the factory member function hides it from you to prevent accidental misuse. If you want to use the object without polymorphism, you must explicitly use RTTI and casting. But there's a little problem, especially when you use the more complicated approach (not shown here) of making the factory member function in the base class and overriding it in the derived classes. What if the information required in the derived class requires more or different arguments? Creating more objects solves this problem. To implement the factory member function, the Trash class gets a new member function called factory. To hide the creational data, there's a new class called Info that contains all of the necessary information for the factory method to create the appropriate Trash object. Now, if there's a situation in which factory( ) needs more or different information to create a new type of Trash object, the factory( ) interface doesn't need to be changed. The Info class can be changed by adding new data and new constructors, or in the more typical object-oriented fashion of subclassing. The point is that it's now hidden away in one place, and you know to come to this place when you add new types. Of course, if you change the quantity and type of argument, this statement will still need to be modified, but that can be eliminated if the creation of the Info object is automated. For example, a Vector of arguments can be passed into the constructor of an Info object (or directly into a factory( ) call, for that matter). This requires that the arguments be parsed and checked at runtime, but it does provide the greatest flexibility. A pattern for prototyping creation A problem with the design above is that it still requires a central location where all the types of the objects must be known: inside the factory( ) method. If new types are regularly being added to the system, the factory( ) method must be changed for each new type. When you discover something like this, it is useful to try to go one step further and move all of the information about the type - including its creation - into the class representing that type. This way, the only thing you need to do to add a new type to the system is to inherit a single class. To move the information concerning type creation into each specific type of Trash, the prototype pattern (from the Design Patterns book) will be used. The general idea is that you have a master sequence of objects, one of each type you're interested in making. The objects in this sequence are used only for making new objects, using an operation that's not unlike the clone( ) scheme built into Java's root class Object. In this case, we'll name the cloning member function tClone( ). When you find one that matches your needs, you clone it. In this scheme there is no hard-coded information for creation. Each object knows how to expose appropriate information and how to clone itself. Thus, the factory( ) method doesn't need to be changed when a new type is added to the system. One approach to the problem of prototyping is to add a number of methods to support the creation of new objects. However, in Java 1.1 there's already support for creating new objects if you have a handle to the Class object. With Java 1.1 reflection (introduced in Chapter 11) you can call a constructor even if you have only a handle to the Class object. This is the perfect solution for the prototyping problem. The list of prototypes will be represented indirectly by a list of handles to all the Class objects you want to create. In addition, if the prototyping fails, the factory( ) method will assume that it's because a particular Class object wasn't in the list, and it will attempt to load it. By loading the prototypes dynamically like this, the Trash class doesn't need to know what types it is working with, so it doesn't need any modifications when you add new types. This allows it to be easily reused throughout the rest of the chapter. The rest of the class supports the prototyping pattern. You first see two inner classes (which are made static, so they are inner classes only for code organization purposes) describing exceptions that can occur. This is followed by a Vector trashTypes, which is used to hold the Class handles. In Trash.factory( ), the String inside the Info object id (a different version of the Info class than that of the prior discussion) contains the type name of the Trash to be created; this String is compared to the Class names in the list. If there's a match, then that's the object to create. Of course, there are many ways to determine what object you want to make. This one is used so that information read in from a file can be turned into objects. Once you've discovered which kind of Trash to create, then the reflection methods come into play. The getConstructor( ) member function takes an argument that's an array of Class handles. This array represents the arguments, in their proper order, for the constructor that you're looking for. It's also possible, for a more flexible solution, to call getConstructors( ), which returns an array of the possible constructors. What comes back from getConstructor( ) is a handle to a Constructor object (part of java.lang.reflect). You call the constructor dynamically with the member function newInstance( ), which takes an array of Object containing the actual arguments. The process of calling newInstance( ) extracts the double, but you can see it is a bit confusing - an argument might be a double or a Double, but when you make the call you must always pass in a Double. Fortunately, this issue exists only for the primitive types. Once you understand how to do it, the process of creating a new object given only a Class handle is remarkably simple. Reflection also allows you to call methods in this same dynamic fashion. Of course, the appropriate Class handle might not be in the trashTypes list. In this case, the return in the inner loop is never executed and you'll drop out at the end. Here, the program tries to rectify the situation by loading the Class object dynamically and adding it to the trashTypes list. If it still can't be found something is really wrong, but if the load is successful then the factory method is called recursively to try again. Trash subclasses To fit into the prototyping scheme, the only thing that's required of each new subclass of Trash is that it contain a constructor that takes a double argument. Java 1.1 reflection handles everything else. Parsing Trash from an external file The information about Trash objects will be read from an outside file. The trim( ) member function removes white space at both ends of a string. However, other types of collections can be used as well. Of course, Vector doesn't implement Fillable, so it won't work. Since Vector is used in most of the examples, it makes sense to add a second overloaded fillBin( ) member function that takes a Vector. Alternatively, the collection class can provide its own adapter that implements Fillable. The process of opening the data file containing Trash descriptions and the parsing of that file have been wrapped into the static member function ParseTrash.fillBin( ), so now it's no longer a part of our design focus. You will see that throughout the rest of the chapter, no matter what new classes are added, ParseTrash.fillBin( ) will continue to work without change, which indicates a good design. In terms of object creation, this design does indeed severely localize the changes you need to make to add a new type to the system. However, there's a significant problem in the use of RTTI that shows up clearly here. The program seems to run fine, and yet it never detects any cardboard, even though there is cardboard in the list! This happens because of the use of RTTI, which looks for only the types that you tell it to look for. The clue that RTTI is being misused is that every type in the system is being tested, rather than a single type or subset of types. As you will see later, there are ways to use polymorphism instead when you're testing for every type. But if you use RTTI a lot in this fashion, and you add a new type to your system, you can easily forget to make the necessary changes in your program and produce a difficult-to-find bug. So it's worth trying to eliminate RTTI in this case, not just for aesthetic reasons - it produces more maintainable code. Abstracting usage With creation out of the way, it's time to tackle the remainder of the design: where the classes are used. Since it's the act of sorting into bins that's particularly ugly and exposed, why not take that process and hide it inside a class? This is the principle of If you must do something ugly, at least localize the ugliness inside a class. It looks like this: The TrashSorter object initialization must now be changed whenever a new type of Trash is added to the model. How does the statically-coded member function deal with the fact that a new type has been added? To solve this, the type information must be removed from sort( ) so that all it needs to do is call a generic member function that takes care of the details of type. This, of course, is another way to describe a dynamically-bound member function. So sort( ) will simply move through the sequence and call a dynamically-bound member function for each Vector. Since the job of this member function is to grab the pieces of trash it is interested in, it's called grab(Trash). The structure now looks like: TrashSorter needs to call each grab( ) member function and get a different result depending on what type of Trash the current Vector is holding. That is, each Vector must be aware of the type it holds. The classic approach to this problem is to create a base Trash bin class and inherit a new derived class for each different type you want to hold. If Java had a parameterized type mechanism that would probably be the most straightforward approach. But rather than hand-coding all the classes that such a mechanism should be building for us, further observation can produce a better approach. But what it does is strictly dependent on the type, and nothing else. This could be interpreted as a different state, and since Java has a class to represent type (Class) this can be used to determine the type of Trash a particular Tbin will hold. The constructor for this Tbin requires that you hand it the Class of your choice. This tells the Vector what type it is supposed to hold. Then the grab( ) member function uses Class BinType and RTTI to see if the Trash object you've handed it matches the type it's supposed to grab. Here is the whole program. The commented numbers (e.g. (*1*) ) mark sections that will be described following the code. 2. sortBin( ) allows you to pass an entire Tbin in, and it moves through the Tbin, picks out each piece of Trash, and sorts it into the appropriate specific Tbin. Notice the genericity of this code: it doesn't change at all if new types are added. If the bulk of your code doesn't need changing when a new type is added (or some other change occurs) then you have an easily-extensible system. 3. Now you can see how easy it is to add a new type. Few lines must be changed to support the addition. If it's really important, you can squeeze out even more by further manipulating the design. 4. One member function call causes the contents of bin to be sorted into the respective specifically-typed bins. Multiple dispatching The above design is certainly satisfactory. Adding new types to the system consists of adding or modifying distinct classes without causing code changes to be propagated throughout the system. In addition, RTTI is not misused as it was in RecycleA.java. However, it's possible to go one step further and take a purist viewpoint about RTTI and say that it should be eliminated altogether from the operation of sorting the trash into bins. The previous examples first sorted by type, then acted on sequences of elements that were all of a particular type. But whenever you find yourself picking out particular types, stop and think. The whole idea of polymorphism (dynamically-bound member function calls) is to handle type-specific information for you. So why are you hunting for types? The answer is something you probably don't think about: Java performs only single dispatching. That is, if you are performing an operation on more than one object whose type is unknown, Java will invoke the dynamic binding mechanism on only one of those types. This doesn't solve the problem, so you end up detecting some types manually and effectively producing your own dynamic binding behavior. The solution is called multiple dispatching, which means setting up a configuration such that a single member function call produces more than one dynamic member function call and thus determines more than one type in the process. To get this effect, you need to work with more than one type hierarchy: you'll need a type hierarchy for each dispatch. The following example works with two hierarchies: the existing Trash family and a hierarchy of the types of trash bins that the trash will be placed into. This second hierarchy isn't always obvious and in this case it needed to be created in order to produce multiple dispatching (in this case there will be only two dispatches, which is referred to as double dispatching). In the Trash hierarchy there will be a new member function called addToBin( ), which takes an argument of an array of TypedBin. It uses this array to step through and try to add itself to the appropriate bin, and this is where you'll see the double dispatch. The new hierarchy is TypedBin, and it contains its own member function called add( ) that is also used polymorphically. But here's an additional twist: add( ) is overloaded to take arguments of the different types of trash. So an essential part of the double dispatching scheme also involves overloading. Redesigning the program produces a dilemma: it's now necessary for the base class Trash to contain an addToBin( ) member function. One approach is to copy all of the code and change the base class. Another approach, which you can take when you don't have control of the source code, is to put the addToBin( ) member function into an interface, leave Trash alone, and inherit new specific types of Aluminum, Paper, Glass, and Cardboard. This is the approach that will be taken here. Most of the classes in this design must be public, so they are placed in their own files. But notice the argument: this. The type of this is different for each subclass of Trash, so the code is different. During the call to add( ), this information is passed via the type of this. The compiler resolves the call to the proper overloaded version of add( ). That is the second dispatch. In each of the subclasses of TypedBin, only one overloaded member function is overridden, according to the type of bin that's being created. For example, CardboardBin overrides add(DDCardboard). The overridden member function adds the trash object to its collection and returns true, while all the rest of the add( ) methods in CardboardBin continue to return false, since they haven't been overridden. This is another case in which a parameterized type mechanism in Java would allow automatic generation of code. You can see that once the structure is set up, sorting into the various TypedBins is remarkably easy. In addition, the efficiency of two dynamic member function calls is probably better than any other way you could sort. Notice the ease of use of this system in main( ), as well as the complete independence of any specific type information within main( ). All other methods that talk only to the Trash base-class interface will be equally invulnerable to changes in Trash types. The visitor pattern Now consider applying a design pattern with an entirely different goal to the trash-sorting problem. For this pattern, we are no longer concerned with optimizing the addition of new types of Trash to the system. Indeed, this pattern makes adding a new type of Trash more complicated. The assumption is that you have a primary class hierarchy that is fixed; perhaps it's from another vendor and you can't make changes to that hierarchy. However, you'd like to add new polymorphic methods to that hierarchy, which means that normally you'd have to add something to the base class interface. So the dilemma is that you need to add methods to the base class, but you can't touch the base class. How do you get around this? The design pattern that solves this kind of problem is called a visitor (the final one in the Design Patterns book), and it builds on the double dispatching scheme shown in the last section. The visitor pattern allows you to extend the interface of the primary type by creating a separate class hierarchy of type Visitor to virtualize the operations performed upon the primary type. The objects of the primary type simply accept the visitor, then call the visitor's dynamically-bound member function. This configuration means that new functionality can be added to the system in the form of new subclasses of Visitor. The Trash hierarchy doesn't need to be touched. This is the prime benefit of the visitor pattern: you can add new polymorphic functionality to a class hierarchy without touching that hierarchy (once the accept( ) methods have been installed). Note that the benefit is helpful here but not exactly what we started out to accomplish, so at first blush you might decide that this isn't the desired solution. But look at one thing that's been accomplished: the visitor solution avoids sorting from the master Trash sequence into individual typed sequences. Thus, you can leave everything in the single master sequence and simply pass through that sequence using the appropriate visitor to accomplish the goal. Although this behavior seems to be a side effect of visitor, it does give us what we want (avoiding RTTI). The double dispatching in the visitor pattern takes care of determining both the type of Trash and the type of Visitor. In the following example, there are two implementations of Visitor: PriceVisitor to both determine and sum the price, and WeightVisitor to keep track of the weights. You can see all of this implemented in the new, improved version of the recycling program. Now there's only a single Trash bin. The two Visitor objects are accepted into every element in the sequence, and they perform their operations. The visitors keep their own internal data to tally the total weights and prices. Finally, there's no run-time type identification other than the inevitable cast to Trash when pulling things out of the sequence. This, too, could be eliminated with the implementation of parameterized types in Java. More coupling? There's a lot more code here, and there's definite coupling between the Trash hierarchy and the Visitor hierarchy. However, there's also high cohesion within the respective sets of classes: they each do only one thing (Trash describes Trash, while Visitor describes actions performed on Trash), which is an indicator of a good design. Of course, in this case it works well only if you're adding new Visitors, but it gets in the way when you add new types of Trash. Low coupling between classes and high cohesion within a class is definitely an important design goal. Applied mindlessly, though, it can prevent you from achieving a more elegant design. It seems that some classes inevitably have a certain intimacy with each other. These often occur in pairs that could perhaps be called couplets, for example, collections and iterators (Enumerations). The Trash-Visitor pair above appears to be another such couplet. RTTI considered harmful? Various designs in this chapter attempt to remove RTTI, which might give you the impression that it's considered harmful (the condemnation used for poor, ill-fated goto, which was thus never put into Java). This isn't true; it is the misuse of RTTI that is the problem. The reason our designs removed RTTI is because the misapplication of that feature prevented extensibility, while the stated goal was to be able to add a new type to the system with as little impact on surrounding code as possible. However, RTTI doesn't automatically create non-extensible code. Let's revisit the trash recycler once more. This time, a new tool will be introduced, which I call a TypeMap. It contains a Hashtable that holds Vectors, but the interface is simple: you can add( ) a new object, and you can get( ) a Vector containing all the objects of a particular type. The keys for the contained Hashtable are the types in the associated Vector. The beauty of this design (suggested by Larry O'Brien) is that the TypeMap dynamically adds a new pair whenever it encounters a new type, so whenever you add a new type to the system (even if you add the new type at run-time), it adapts. It contains a Hashtable, and the add( ) member function does most of the work. When you add( ) a new object, the handle for the Class object for that type is extracted. This is used as a key to determine whether a Vector holding objects of that type is already present in the Hashtable. If so, that Vector is extracted and the object is added to the Vector. If not, the Class object and a new Vector are added as a key-value pair. You can get an Enumeration of all the Class objects from keys( ), and use each Class object to fetch the corresponding Vector with get( ). And that's all there is to it. You never need a named class to implement Fillable, you just need a handle to an object of that class, thus this is an appropriate use of anonymous inner classes. An interesting thing about this design is that even though it wasn't created to handle the sorting, fillBin( ) is performing a sort every time it inserts a Trash object into bin. Much of class DynaTrash should be familiar from the previous examples. This time, instead of placing the new Trash objects into a bin of type Vector, the bin is of type TypeMap, so when the trash is thrown into bin it's immediately sorted by TypeMap's internal sorting mechanism. This is certainly the smallest solution to the problem, and arguably the most elegant as well. It does rely heavily on RTTI, but notice that each key-value pair in the Hashtable is looking for only one type. In addition, there's no way you can forget to add the proper code to this system when you add a new type, since there isn't any code you need to add. Summary Coming up with a design such as TrashVisitor.java that contains a larger amount of code than the earlier designs can seem at first to be counterproductive. It pays to notice what you're trying to accomplish with various designs. Design patterns in general strive to separate the things that change from the things that stay the same. The things that change can refer to many different kinds of changes. Perhaps the change occurs because the program is placed into a new environment or because something in the current environment changes (this could be: The user wants to add a new shape to the diagram currently on the screen). Or, as in this case, the change could be the evolution of the code body. While previous versions of the trash-sorting example emphasized the addition of new types of Trash to the system, TrashVisitor.java allows you to easily add new functionality without disturbing the Trash hierarchy. There's more code in TrashVisitor.java, but adding new functionality to Visitor is cheap. If this is something that happens a lot, then it's worth the extra effort and code to make it happen more easily. The discovery of the vector of change is no trivial matter; it's not something that an analyst can usually detect before the program sees its initial design. The necessary information will probably not appear until later phases in the project: sometimes only at the design or implementation phases do you discover a deeper or more subtle need in your system. In the case of adding new types (which was the focus of most of the recycle examples) you might realize that you need a particular inheritance hierarchy only when you are in the maintenance phase and you begin extending the system! One of the most important things that you'll learn by studying design patterns seems to be an about-face from what has been promoted so far in this book. That is: OOP is all about polymorphism. This statement can produce the two-year-old with a hammer syndrome (everything looks like a nail). Put another way, it's hard enough to get polymorphism, and once you do, you try to cast all your designs into that one particular mold. What design patterns say is that OOP isn't just about polymorphism. But design patterns in general show other ways to accomplish the basic goal, and once your eyes have been opened to this you will begin to search for more creative designs. Since the Design Patterns book came out and made such an impact, people have been searching for other patterns. You can expect to see more of these appear as time goes on. Exercises 1. Using SingletonPattern.java as a starting point, create a class that manages a fixed number of its own objects. 2. Add a class Plastic to TrashVisitor.java. 3. Add a class Plastic to DynaTrash.java. Then run the program in this section to extract all the code files and place them in appropriate subdirectories. You've seen that each file to be extracted contains a starting marker (which includes the file name and path) and an ending marker. Files can be of any type, and if the colon after the comment is directly followed by a '!' then the starting and ending marker lines are not reproduced in the generated file. If there's a mistake in the input file, then the program must report the error, which is the error( ) function at the beginning of the program. In addition, directory manipulation is not supported by the standard libraries, so this is hidden away in the class OSDirControl. If you discover that this class will not compile on your system, you must replace the non-portable function calls in OSDirControl with eqivalent calls from your library. The macro D( ) was used to help debug the program. For example, you can say D(a + b). This macro is left in the listing to aid you if you need to port the program to some other compiler or operating system. To allow these to be changed at execution time, a class called ProgVals is inherited from a map of strings to strings. This is accomplished by using a static ofstream object to hold the error messages - the file is created the first time error( ) is called and the ofstream destructor closes it. The count of the number of errors is held in a static object of the inner class ErrReport. Again, this object is only created the first time error( ) is called, and if that happens then its destructor is called when the program exits, thus producing the error count upon program termination. The job of a PushDirectory object is to capture the current directory, then created and move down each directory in the path (the path can be arbitrarily long). The destructor returns the path to the one that was captured before all the creating and moving took place. In main( ), the input file is opened and each line is read. The extractFile( ) function does the bulk of the work. First it checks to see if this file doesn't want its beginning and ending tag lines to be output. The string file initially holds the entire path and file data, but string::find_last_of( ) is used to create path (containing all the path information) and trim file so it only contains the file name. The creation of the PushDirectory object creates the necessary directories and moves to the right one (and the destructor restores the original directory). The output file is created and this creation is reported to cout. Once the end tag is found, that file is complete. Debugging This section contains some tips and techniques which may help during debugging. It's brief, to the point and portable. In addition, when you're finished debugging you can remove all the code by defining NDEBUG, either on the command-line or in code. Also, assert( ) can be used while roughing out the code. Later, the calls to assert( ) that are actually providing information to the end user can be replaced with more civilized messages. Trace macros Sometimes it's very helpful to print the code of each statement before it is executed, either to cout or to a trace file. Of course, it can introduce problems. Thus, this technique must be used carefully. Trace file This code allows you to easily create a trace file and send all the output that would normally go to cout into the file. Then everyone on the team must inherit from this class and redefine the debugging functions. All objects in the system will then have debugging functions available. This section provides a system to help you track these kinds of problems down. To use the memory checking system, you simply link the obj file in and all the calls to malloc( ), realloc( ), calloc( ), free( ), new and delete are intercepted. However, if you also include the following file (which is optional), all the calls to new will store information about the file and line where they were called. This is accomplished with a use of the placement syntax for operator new (this trick was suggested by Reg Charney of the C++ Standards Committee). The placement syntax is intended for situations where you need to place objects at a specific point in memory. However, it allows you to create an operator new with any number of arguments. This is because, for example, the cout constructor allocates memory. Standard IO ensures against cyclical conditions that can lock up the system. The operator FILE*( ) allows you to simply use the OFile object anyplace you would ordinarily use a FILE* (in the fprintf( ) statements in this example). The #define that follows simply sends everything to standard output, but if you need to put it in a trace file you simply comment out that line. Memory is allocated from an array called _memory_pool. The _pool_ptr is moved forward every time storage is allocated. For simplicity, the storage is never reclaimed, and realloc( ) doesn't try to resize the storage in the same place. All the storage allocation functions call getmem( ) which ensures there is enough space left and moves the _pool_ptr to allocate your storage. The MemBag class is the heart of the system. You will see many similarities to xbag in MemBag. A distinct difference is realloc( ) is replaced by a call to getmem( ) and memmove( ), so that storage allocated for the MemBag is not registered. In addition, the type enum allows you to store the way the memory was allocated; the typestr( ) function takes a type and produces a string for use with printing. The nested struct m holds the pointer, the type, a pointer to the file name (which is assumed to be statically allocated) and the line where the allocation occurred. The allocation( ) function prints out a different message depending on whether the storage was allocated with new (where it has line and file information) or malloc( ) (where it doesn't). The following is a simple test using the memcheck facility. CGI programming in C++ The World-Wide Web has become the common tongue of connectivity on planet earth. It began as simply a way to publish primitively-formatted documents in a way that everyone could read them regardless of the machine they were using. The documents are created in hypertext markup language (HTML) and placed on a central server machine where they are handed to anyone who asks. The documents are requested and read using a web browser that has been written or ported to each particular platform. Very quickly, just reading a document was not enough and people wanted to be able to collect information from the clients, for example to take orders or allow database lookups from the server. Many different approaches to client-side programming have been tried such as Java applets, Javascript, and other scripting or programming languages. Unfortunately, whenever you publish something on the Internet you face the problem of a whole history of browsers, some of which may support the particular flavor of your client-side programming tool, and some which won't. The Web server takes an encoded request submitted via an HTML page and responds by invoking a CGI program and handing it the encoded data from the request. CGI can seem a bit intimidating at first, but it turns out that it's just messy, and not all that difficult to write. However, there is some decoding that must be done in order to extract the data that's been sent to you from the client's web page. In this section you'll get a crash course in CGI programming, and we'll develop tools that will perform the decoding for the two different types of CGI submissions (GET and POST). These tools will allow you to easily write a CGI program to solve any problem. Since C++ exists on virtually all machines that have Web servers (and you can get GNU C++ free for virtually any platform), the solution presented here is quite portable. Encoding data for CGI To submit data to a CGI program, the HTML form tag is used. The method can be either get or post, and the action is what the server does when it receives the form data: it calls a program. Each form has a method, an action, and a submit button, and the rest of the form consists of input fields. The most commonly-used input field is shown here: a text field. However, you can also have things like check boxes, drop-down selection lists and radio buttons. By using the .exe extension the program can be tested without change under most operating systems. For one thing, spaces are not allowed (since spaces typically separate command-line arguments). Spaces are replaced by '+' signs. This is encoded to: John+%26+Marsha+Smith That is, the special character is turned into a '%' followed by its ASCII value in hex. Fortunately, the web browser automatically performs all encoding for you. The CGI parser There are many examples of CGI programs written using Standard C. One argument for doing this is that Standard C can be found virtually everywhere. However, C++ has become quite ubiquitous, especially in the form of the GNU C++ Compiler72 (g++) that can be downloaded free from the Internet for virtually any platform (and often comes pre-installed with operating systems such as Linux). As you will see, this means that you can get the benefit of object-oriented programming in a CGI program. One of the reasons for using C++ here is the convenience of the STL, in particular the map class. The map template will be used in the creation of CGImap, which you'll see is a fairly short definition considering how powerful it is. The project will start with a reusable portion, which consists of CGIpair and CGImap in a header file. The second constructor calls the member function decodeURLString( ) which produces a string after stripping away all the extra characters added by the browser as it submitted the CGI request. There is no need to provide functions to select each individual element - because pair is inherited publicly, you can just select the first and second elements of the CGIpair. The operator bool provides automatic type conversion to bool. Because the string objects take care of themselves, you don't need to explicitly define the copy-constructor, operator= or destructor - the default versions synthesized by the compiler do the right thing. The remainder of the CGIpair class consists of the two methods decodeURLString( ) and a helper member function translateHex( ) which is used by decodeURLString( ). CGImap parses and holds all the name-value pairs submitted from the form as part of a CGI request. You can also see that CGImap has a copy-constructor and an operator=, but they're both declared as private. This is to prevent the compiler from synthesizing the two functions (which it will do if you don't declare them yourself), but it also prevents the client programmer from passing a CGImap by value or from using assignment. CGImap's job is to take the input data and parse it into name-value pairs, which it will do with the aid of CGIpair (effectively, CGIpair is only a helper class, but it also seems to make it easier to understand the code). After copying the query string (you'll see where the query string comes from later) into a local string object gq, the nextPair( ) member function is used to parse the string into raw name-value pairs, delimited by '=' and 'and' signs. Each resulting CGIpair object is added to the vector using the standard vector::push_back( ). When nextPair( ) runs out of input from the query string, it returns zero. Since the CGImap is intentionally not sorted and they tend to be small, this is not too terrible. The dump( ) function is used for testing, typically by sending information to the resulting Web page, as you might guess from the default value of nl, which is an HTML break line token. Using GET can be fine for many applications. However, GET passes its data to the CGI program through an environment variable (called QUERY_STRING), and operating systems typically run out of environment space with long GET strings (you should start worrying at about 200 characters). CGI provides a solution for this: POST. With POST, the data is encoded and concatenated the same way as with GET, but POST uses standard input to pass the encoded query string to the CGI program and has no length limitation on the input. All you have to do in your CGI program is determine the length of the query string. This length is stored in the environment variable CONTENT_LENGTH. Once you know the length, you can allocate storage and read the precise number of bytes from standard input. Because POST is the less-fragile solution, you should probably prefer it over GET, unless you know for sure that your input will be short. In fact, one might surmise that the only reason for GET is that it is slightly easier to code a CGI program in C using GET. However, the last class in CGImap.h is a tool that makes handling a POST just as easy as handling a GET, which means you can always use POST. The class Post inherits from a string and only has a constructor. The job of the constructor is to get the query data from the POST into itself (a string). It does this by reading the CONTENT_LENGTH environment variable using the Standard C library function getenv( ). This comes back as a pointer to a C character string. If this pointer is zero, the CONTENT_LENGTH environment variable has not been set, so something is wrong. Otherwise, the character string must be converted to an integer using the Standard C library function atoi( ). The resulting length is used with new to allocate enough storage to hold the query string (plus its null terminator), and then read( ) is called for cin. The read( ) function takes a pointer to the destination buffer and the number of bytes to read. The resulting buffer is inserted into the current string using string::append( ). At this point, the POST data is just a string object and can be easily used without further concern about where it came from. Testing the CGI parser Now that the basic tools are defined, they can easily be used in a CGI program like the following which simply dumps the name-value pairs that are parsed from a GET query. So to read that information all you have to do is get the QUERY_STRING. You do this with the standard C library function getenv( ), passing it the identifier of the environement variable you wish to fetch. In main( ), notice how simple the act of parsing the QUERY_STRING is: you just hand it to the constructor for the CGImap object called query and all the work is done for you. A CGI program is handed its input in one of two ways: through QUERY_STRING during a GET (as in the above case) or through standard input during a POST. But a CGI program only returns its results through standard output, via cout. Where does this output go? Back to the Web server, which decides what to do with it. The server makes this decision based on the content-type header, which means that if the content-type header isn't the first thing it sees, it won't know what to do with the data. Thus it's essential that you start the output of all CGI programs with the content-type header. In this case, we want the server to feed all the information directly back to the client program. Once the server sees this, it will echo all strings right back to the client as a simple text Web page. To test this program, you must compile it in the cgi-bin directory of your host Web server. It's also quite cheap and easy to get an old PC and install Linux along with an inexpensive network card. Linux automatically sets up the Apache server for you, and you can test everything on your local network as if it were live on the Internet. One way or another it's possible to install a Web server for local tests, so you don't need to have a remote Web server and permission to install CGI programs on that server. One of the advantages of this design is that, now that CGIpair and CGImap are defined, most of the work is done for you so you can easily create your own CGI program simply by modifying main( ). Using POST The CGIpair and CGImap from CGImap.h can be used as is for a CGI program that handles POSTs. The only thing you need to do is get the data from a Post object instead of from the QUERY_STRING environment variable. The different fields in the vector are then available just as in the previous example. The server turns around and feeds the query string to the CGI program via standard input. Handling mailing lists Managing an email list is the kind of problem many people need to solve for their Web site. As it is turning out to be the case for everything on the Internet, the simplest approach is always the best. I learned this the hard way, first trying a variety of Java applets (which some firewalls do not allow) and even JavaScript (which isn't supported uniformly on all browsers). The result of each experiment was a steady stream of email from the folks who couldn't get it to work. When you set up a Web site, your goal should be to never get email from anyone complaing that it doesn't work, and the best way to produce this result is to use plain HTML (which, with a little work, can be made to look quite decent). The second problem was on the server side. Ideally, you'd like all your email addresses to be added and removed from a single master file, but this presents a problem. Most operating systems allow more than one program to open a file. When a client makes a CGI request, the Web server starts up a new invocation of the CGI program, and since a Web server can handle many requests at a time, this means that you can have many instances of your CGI program running at once. If the CGI program opens a specific file, then you can have many programs running at once that open that file. This is a problem if they are each reading and writing to that file. There may be a function for your operating system that locks a file, so that other invocations of your program do not access the file at the same time. However, I took a different approach, which was to make a unique file for each client. Making a file unique was quite easy, since the email name itself is a unique character string. The filename for each request is then just the email name, followed by the string.add or .remove. The contents of the file is also the email address of the client. The HTML code to place on your Web page becomes fairly straightforward. The subject-field tells the CGI program the subdirectory where the resulting file should be placed. The command-field tells the CGI program whether the user is requesting that they be added or removed from the list. From the action, you can see that a GET is used with a program called mlm.exe (for mailing list manager). From then on it's a matter of pulling the fields out and looking at them, then deciding what to do about it, which is easy because of the way you can index into a map and also because of the tools available for standard strings. Here, most of the programming has to do with checking for a valid email address. Then a file name is created with the email address as the name and.add or .remove as the extension, and the email address is placed in the file. Maintaining your list Once you've got a list of names to add, you can just paste them to end of your list. However, you might get some duplicates so you need a program to remove those. Again, you may want to change the above to the form readln(in, s) instead of using a fixed-sized buffer, which is more fragile. The sort must be performed so that all duplicates are adjacent to each other. Then unique( ) can remove all the adjacent duplicates. The program also keeps track of how many duplicate names were removed. Although there is a remove( ) algorithm that can be applied to containers, the built-in list::remove( ) seems to work better. The second command-line argument is the file containing the list of names to be removed. An iterator is used to step through that list, and the list::remove( ) function removes every instance of each name from the master list. Here, the list doesn't need to be sorted first. Unfortunately, that's not all there is to it. The messiest part about maintaining a mailing list is the bounced messages. Presumably, you'll just want to remove the addresses that produce bounces. If you can combine all the bounced messages into a single file, the following program has a pretty good chance of extracting the email addresses; then you can use RemoveGroup to delete them from your list. This is not because of any particular design insight. So the C is just because it works, and you may be able to rewrite the program in more pure C++ using your C++ compiler and produce correct results. A lot of what this program does is read lines looking for string matches. To make this convenient, I created a StringSet class with a member function in( ) that tells you whether any of the strings in the set are in the argument. The StringSet is initialized with a constant two-dimensional of strings and the size of that array. Although the StringSet makes the code easier to read, it's also easy to add new strings to the arrays. The continues StringSet contains strings whose lines should be ignored. Using a set eliminates duplicates (you may have duplicates based on case, but those are dealt with by RemoveGroup.cpp. The resulting set of names is then printed to the output file. The program spends all its time building the external command. When people don't want to be on a list anymore they will often ignore instructions and just reply to the message. This can be a problem if the email address they're replying with is different than the one that's on your list (sometimes it has been routed to a new or aliased address). To solve the problem, this program prepends the text file with a message that informs them that they can remove themselves from the list by visiting a URL. Since many email programs will present a URL in a form that allows you to just click on it, this can produce a very simple removal process. If you look at the URL, you can see it's a call to the mlm.exe CGI program, including removal information that incorporates the same email address the message was sent to. That way, even if the user just replies to the message, all you have to do is click on the URL that comes back with their reply (assuming the message is automatically copied back to you). Instead, \n click on the following URL, or visit it using your Web browser. This \n way, the proper email address will be removed. The names are read one at a time into the string called name using getline( ). Then a temporary file called m.txt is created to build the customized message for that individual; the customization is the note about how to remove themselves, along with the URL. Then the message body, which is in the file specified by the second command-line argument, is appended to m.txt. Finally, the command is built inside a string: the -F argument to fastmail is who it's from, the -r argument is who to reply to. The -s is the subject line, the next argument is the file containing the mail and the last argument is the email address to send it to. You can start this program in the background and tell Unix not to stop the program when you sign off of the server. However, it takes a while to run for a long list (this isn't because of the program itself, but the mailing process). I like to keep track of the progress of the program by sending a status message to another email account, which is accomplished in the last few lines of the program. A general information-extraction CGI program One of the problems with CGI is that you must write and compile a new program every time you want to add a new facility to your Web site. However, much of the time all that your CGI program does is capture information from the user and store it on the server. If you could use hidden fields to specify what do do with the information, then it would be possible to write a single CGI program that would extract the information from any CGI request. If you can do this, then you can create a new data-collection page just by defining the HTML and creating a new subdirectory on your server. For example, every time I come up with a new class or workshop, all I have to do is create the HTML form for signups - no CGI programming is required. The following HTML page shows the format for this scheme. Since a CGI POST is more general and doesn't have any limit on the amount of information it can send, it will always be used instead of a GET for the ExtractInfo.cpp program that will implement this system. Although this form is simple, yours can be as complicated as you need it. The value of this field named subject-field is used by ExtractInfo.cpp to determine the subdirectory in which to place the resulting file (in this case, the subdirectory will be test-extract-info). Because of this technique and the generality of the program, the only thing you'll usually need to do to start a new database of data is to create the subdirectory on the server and then create an HTML page like the one above. The ExtractInfo.cpp program will do the rest for you by creating a unique file for each submission. Of course, you can always change the program if you want it to do something more unusual, but the system as shown will work most of the time. The contents of the reminder field will be displayed on the form that is sent back to the user when their data is accepted. The test-field indicates whether to dump test information to the resulting Web page. If mail-copy exists and contains anything other than no the value string will be parsed for mailing addresses separated by ';' and each of these addresses will get a mail message with the data in it. The email-address field is required in each case and the email address will be checked to ensure that it conforms to some basic standards. The confirmation field causes a second program to be executed when the form is posted. The design of the confirmation field allows the person creating the HTML page to select more than one type of confirmation. You can make changes to the storage format by modifying store( ), and to the way the data is displayed by modifying show( ). The rest of the program is similar to mlm.cpp because it looks at the test-field and email-address (checking it for correctness). The file name combines the user's email address and the current date and time in hex - notice that sprintf( ) is used because it has a convenient way to convert a value to a hex representation. The entire file and path information is stored in the file, along with all the data from the form, which is tagged as it is stored so that it's easy to parse (you'll see a program to parse the files a bit later). All the information is also sent back to the user as a simply-formatted HTML page, along with the reminder, if there is one. If mail-copy exists and is not no, then the names in the mail-copy value are parsed and an email is sent to each one containing the tagged data. That program will be created in the next section. Parsing the data files You now have a lot of data files accumulating on your Web site, as people sign up for whatever you're offering. Now, if your event is compelling you'll have a whole lot of these files and what you'd like to do is automatically extract the information from them and put that data in any format you'd like. For example, the ProcessApplication.exe program mentioned above will use the data in an email confirmation message. You'll also probably want to put the data in a form that can be easily brought into a spreadsheet. When you create a DataPair, the constructor calls get( ) to extract the next pair from the input stream. The operator bool indicates an empty DataPair, which usually signals the end of an input stream. The FormData constructor is given a file name to open and read. The FormData object alwas expects there to be a file path and an email address, so it reads those itself before getting the rest of the data as DataPairs. You will receive further updates via email. So to be extra careful, the email address is appended onto the end of the temporary file name. Here, no ampersand is used, so system( ) does not return until the command is finished - which is a good thing, since the next operation is to delete the temporary file which is used in the command. The final operation in this project is to extract the data into an easily-usable form. Here, a tab is used but you can easily change it to something else. Also note that I have checked for the workshop-suggestions field and specifically excluded that, because it tends to be too long for the information I want in a spreadsheet. You can make another version of this program that only extracts the workshop-suggestions field. This program assumes that all the file names are expanded on the command line. Install and test ExtractInfo.cpp as a CGI program, using INFOtest.html. 3. Create a program called ExtractSuggestions.cpp that is a modification of DataToSpreadsheet.cpp which will only extract the suggestions along with the name and email addresse of the person that made them. A: Coding style This appendix is not about indenting and placement of parentheses and curly braces, although that will be mentioned. This is about the general guidelines used in this book for organizing the code listings. All the decisions about coding style in this book have been deliberatly made and considered, sometimes over a period of years. Of course, everyone has their reasons for organizing code the way they do, and I'm just trying to tell you how I arrived at mine and the constraints and environmental factors that brought me to those decisions. Begin and end comment tags A very important issue with this book is that all code that you see in the book must be automatically extractable and compilable, so it can be verified to be correct (with at least one compiler). To facilitate this, all code listings that are meant to be compiled (as opposed to code fragments, of which there are few) have comment tags at the beginning and end. Because ExtractCode.cpp also creates a makefile for each subdirectory, information about how a program is made and the command-line used to test it is also incorporated into the listings. If a program is stand alone (it doesn't need to be linked with anything else) it has no extra information. This is also true for header files. Of course, everyone feels their own style is the most rational. However, the style used here has a simple logic behind it, which will be presented here mixed in with ideas on why some of the other styles developed. The formatting style is motivated by one thing: presentation, both in print and in live seminars. You may feel your needs are different because you don't make a lot of presentations, but working code is read much more than it is written, so it should be easy for the reader to perceive. My two most important criteria are scannability (how easy it is for the reader to grasp the meaning of a single line) and the number of lines that can fit on a page. This latter may sound funny, but when you are giving a live presentation, it's very distracting to shuffle back and forth between slides, and a few wasted lines can cause this. Everyone seems to agree that code inside braces should be indented. What people don't agree on, and the place where there's the most inconsistency within formatting styles is this: where does the opening brace go? This one question, I feel, is what causes such inconsistencies among coding styles (For an enumeration of coding styles, see C++ Programming Guidelines, by Tom Plum and Dan Saks, Plum Hall 1991). I'll try to convince you that many of today's coding styles come from pre-Standard C constraints (before function prototypes) and are thus inappropriate now. First, my answer to the question: the opening brace should always go on the same line as the precursor (by which I mean whatever the body is about: a class, function, object definition, if statement, etc.). This is a single, consistent rule I apply to all the code I write, and it makes formatting much simpler. You can tell by looking at the single line in all cases whether it's a declaration or definition. And of course, putting the opening brace on the same line, instead of a line by itself, allows you to fit more lines on a page. So why do we have so many other styles? Stroustrup does this except for short inline functions. With the approach I describe here, everything is consistent - you name whatever it is (class, function, enum, etc) and on that same line you put the opening brace to indicate that the body for this thing is about to follow. Also, the opening brace is the same for short inlines and ordinary function definitions. However, they did make various decisions about whether the braces should be indented with the body of the code, or whether they should be at the level of the precursor. Thus we got many different formatting styles. The approach I use removes all the exceptions and special cases, and logically produces a single style of indentation, as well. The consistency alone, I feel, makes it worthy of consideration. Above all, C++ is a new language and (although we must make many concessions to C) we shouldn't be carrying too many artifacts with us that cause problems in the future. Small problems multiplied by many lines of code become big problems. The other constraint I must work under is the line width, since the book has a limitation of 50 characters. What happens when something is too long to fit on one line? Well, again I strive to have a consistent policy for the way lines are broken up, so they can be easily viewed. In cpp files, any global using definitions will only affect that file, and so they are generally used for ease of reading and writing code, especially in small programs. Many of these tips are summarized from the pages of this book. 4. Don't automatically rewrite all your existing C code in C++ unless you need to significantly change its functionality (that is, don't fix it if it isn't broken). Recompiling in C++ is a very valuable activity because it may reveal hidden bugs. However, taking C code that works fine and rewriting it in C++ may not be the most valuable use of your time, unless the C++ version will provide a lot of opportunities for reuse as a class. 5. Separate the class creator from the class user (client programmer). The class user is the customer and doesn't need or want to know what's going on behind the scenes of the class. The class creator must be the expert in class design and write the class so it can be used by the most novice programmer possible, yet still work robustly in the application. Library use will be easy only if it's transparent. 6. When you create a class, make your names as clear as possible. Your goal should be to make the user's interface conceptually simple. To this end, use function overloading and default arguments to create a clear, easy-to-use interface. 7. Data hiding allows you (the class creator) to change as much as possible in the future without damaging client code in which the class is used. In this light, keep everything as private as possible, and make only the class interface public, always using functions rather than data. Make data public only when forced. If class users don't need to access a function, make it private. If a part of your class must be exposed to inheritors as protected, provide a function interface rather than expose the actual data. In this way, implementation changes will have minimal impact on derived classes. 8. Don't fall into analysis paralysis. Some things you don't learn until you start coding and get some kind of system working. C++ has built-in firewalls; let them work for you. Your mistakes in a class or set of classes won't destroy the integrity of the whole system. 9. Your analysis and design must produce, at minimum, the classes in your system, their public interfaces, and their relationships to other classes, especially base classes. If your method produces more than that, ask yourself if all the elements have value over the lifetime of the program. If they do not, maintaining them will cost you. Members of development teams tend not to maintain anything that does not contribute to their productivity; this is a fact of life that many design methods don't account for. Remember the fundamental rule of software engineering: All problems can be simplified by introducing an extra level of conceptual indirection.74 This one idea is the basis of abstraction, the primary feature of object-oriented programming. Make classes as atomic as possible; that is, give each class a single, clear purpose. If your classes or your system design grows too complicated, break complex classes into simpler ones. From a design standpoint, look for and separate things that change from things that stay the same. That is, search for the elements in a system that you might want to change without forcing a redesign, then encapsulate those elements in classes. Watch out for variance. Two semantically different objects may have identical actions, or responsibilities, and there is a natural temptation to try to make one a subclass of the other just to benefit from inheritance. Watch out for limitation during inheritance. The clearest designs add new capabilities to inherited ones. A suspicious design removes old capabilities during inheritance without adding new ones. Don't extend fundamental functionality by subclassing. If an interface element is essential to a class it should be in the base class, not added during derivation. If you're adding member functions by inheriting, perhaps you should rethink the design. Start with a minimal interface to a class, as small and simple as you need. As the class is used, you'll discover ways you must expand the interface. However, once a class is in use you cannot shrink the interface without disturbing client code. If you need to add more functions, that's fine; it won't disturb code, other than forcing recompiles. But even if new member functions replace the functionality of old ones, leave the existing interface alone (you can combine the functionality in the underlying implementation if you want). Read your classes aloud to make sure they're logical, referring to base classes as is-a and member objects as has-a.. 18. When deciding between inheritance and composition, ask if you need to upcast to the base type. If not, prefer composition (member objects) to inheritance. This can eliminate the perceived need for multiple inheritance. If you inherit, users will think they are supposed to upcast. Sometimes you need to inherit in order to access protected members of the base class. This can lead to a perceived need for multiple inheritance. If you don't need to upcast, first derive a new class to perform the protected access. Then make that new class a member object inside any class that needs to use it, rather than inheriting. Typically, a base class will only be an interface to classes derived from it. When you create a base class, default to making the member functions pure virtual. The destructor can also be pure virtual (to force inheritors to explicitly redefine it), but remember to give the destructor a function body, because all destructors in a hierarchy are always called. When you put a virtual function in a class, make all functions in that class virtual, and put in a virtual destructor. Start removing the virtual keyword when you're tuning for efficiency. This approach prevents surprises in the behavior of the interface. Use data members for variation in value and virtual functions for variation in behavior. That is, if you find a class with state variables and member functions that switch behavior on those variables, you should probably redesign it to express the differences in behavior within subclasses and virtual functions. If you must do something nonportable, make an abstraction for that service and localize it within a class. This extra level of indirection prevents the nonportability from being distributed throughout your program. Avoid multiple inheritance. It's for getting you out of bad situations, especially repairing class interfaces where you don't have control of the broken class (see Chapter 15). You should be an experienced programmer before designing multiple inheritance into your system. Don't use private inheritance. Although it's in the language and seems to have occasional functionality, it introduces significant ambiguities when combined with run-time type identification. Create a private member object instead of using private inheritance. If two classes are associated with each other in some functional way (such as containers and iterators) try to make one a public nested friend class of the other, as the STL does with iterators inside containers. This not only emphasizes the association between the classes, but it allows the class name to be reused by nesting it within another class. Again, the STL does this by placing iterator inside each container class, thereby providing them with a common interface. The other reason you'll want to nest a class is as part of the private implementation. Here, nesting is beneficial for implementation hiding rather than class association and the prevention of namespace pollution as above. Operator overloading is only syntactic sugar: a different way to make a function call. If overloading an operator doesn't make the class interface clearer and easier to use, don't do it. Create only one automatic type conversion operator for a class. In general, follow the guidelines and format given in Chapter 10 when overloading operators. First make a program work, then optimize it. In particular, don't worry about writing inline functions, making some functions nonvirtual, or tweaking code to be efficient when you are first constructing the system. Your primary goal should be to prove the design, unless the design requires a certain efficiency. Don't let the compiler create the constructors, destructors, or the operator= for you. Those are training wheels. Class designers should always say exactly what the class should do and keep the class entirely under control. If you don't want a copy-constructor or operator=, declare them private. Remember that if you create any constructor, it prevents the default constructor from being synthesized. If your class contains pointers, you must create the copy-constructor, operator=, and destructor for the class to work properly. When you write a copy-constructor for a derived class, remember to call the base-class copy-constructor explicitly. If you don't, the default constructor will be called for the base class and that probably isn't what you want. Avoid the preprocessor. Always use const for value substitution and inlines for macros. Keep scopes as small as possible so the visibility and lifetime of your objects are as small as possible. This reduces the chance of using an object in the wrong context and hiding a difficult-to-find bug. For example, suppose you have a container and a piece of code that iterates through it. If you copy that code to use with a new container, you may accidentally end up using the size of the old container as the upper bound of the new one. If, however, the old container is out of scope, the error will be caught at compile time. Avoid global variables. Always strive to put data inside classes. Global functions are more likely to occur naturally than global variables, although you may later discover that a global function may fit better as a static member of a class. If you need to declare a class or function from a library, always do so by including a header file. For example, if you want to create a function to write to an ostream, never declare ostream yourself using an incomplete type specification like this, class ostream; This approach leaves your code vulnerable to changes in representation. When choosing the return type of an overloaded operator, think about chaining expressions together. When defining operator=, remember x=x. Return a copy or reference to the lvalue (return *this) so it can be used in a chained expression (A = B = C). When writing a function, pass arguments by const reference as your first choice. Normally you don't want to be worrying too much about efficiency issues when designing and building your system, but this habit is a sure win. Be aware of temporaries. When tuning for performance, watch out for temporary creation, especially with operator overloading. If your constructors and destructors are complicated, the cost of creating and destroying temporaries can be high. When creating constructors, consider exceptions. In the best case, the constructor won't do anything that throws an exception. In the next-best scenario, the class will be composed and inherited from robust classes only, so they will automatically clean themselves up if an exception is thrown. If you must have naked pointers, you are responsible for catching your own exceptions and then deallocating any resources pointed to before you throw an exception in your constructor. If a constructor must fail, the appropriate action is to throw an exception. Do only what is minimally necessary in your constructors. Not only does this produce a lower overhead for constructor calls (many of which may not be under your control) but your constructors are then less likely to throw exceptions or cause problems. The responsibility of the destructor is to release resources allocated during the lifetime of the object, not just during construction. Use exception hierarchies, preferably derived from the Standard C++ exception hierarchy and nested as public classes within the class that throws the exceptions. The person catching the exceptions can then catch the specific types of exceptions, followed by the base type. If you add new derived exceptions, client code will still catch the exception through the base type. Throw exceptions by value and catch exceptions by reference. Let the exception-handling mechanism handle memory management. If you throw pointers to exceptions created on the heap, the catcher must know to destroy the exception, which is bad coupling. If you catch exceptions by value, you cause extra constructions and destructions; worse, the derived portions of your exception objects may be sliced during upcasting by value. Don't write your own class templates unless you must. Look first in the Standard Template Library, then to vendors who create special-purpose tools. Become proficient with their use and you'll greatly increase your productivity. When creating templates, watch for code that does not depend on type and put that code in a nontemplate base class to prevent needless code bloat. Using inheritance or composition, you can create templates in which the bulk of the code they contain is type-dependent and therefore essential. Don't use the STDIO.H functions such as printf( ). Learn to use iostreams instead; they are type-safe and type-extensible, and significantly more powerful. Your investment will be rewarded regularly (see Chapter 5). In general, always use C++ libraries in preference to C libraries. Avoid C's built-in types. They are supported in C++ for backward compatibility, but they are much less robust than C++ classes, so your bug-hunting time will increase. Whenever you use built-in types as globals or automatics, don't define them until you can also initialize them. Define variables one per line along with their initialization. When defining pointers, put the '*' next to the type name. You can safely do this if you define one variable per line. This style tends to be less confusing for the reader. Guarantee that initialization occurs in all aspects of your code. Perform all member initialization in the constructor initializer list, even built-in types (using pseudo-constructor calls). Use any bookkeeping technique you can to guarantee no uninitialized objects are running around in your system. Don't use the form foo a = b; to define an object. This one feature is a major source of confusion because it calls a constructor instead of the operator=. For clarity, always be specific and use the form foo a(b); instead. The results are identical, but other programmers won't be confused. Use the new casts in C++. A cast overrides the normal typing system and is a potential error spot. By dividing C's one-cast-does-all into classes of well-marked casts, anyone debugging and maintaining the code can easily find all the places where logical errors are most likely to happen. For a program to be robust, each component must be robust. Use all the tools provided by C++: implementation hiding, exceptions, const-correctness, type checking, and so on in each class you create. That way you can safely move to the next level of abstraction when building your system. Build in const-correctness. This allows the compiler to point out bugs that would otherwise be subtle and difficult to find. This practice takes a little discipline and must be used consistently throughout your classes, but it pays off. Use compiler error checking to your advantage. Perform all compiles with full warnings, and fix your code to remove all warnings. Write code that utilizes the compiler errors and warnings rather than that which causes run-time errors (for example, don't use variadic argument lists, which disable all type checking). Use assert( ) for debugging, but use exceptions to work with run-time errors. Prefer compile-time errors to run-time errors. Try to handle an error as close to the point of its occurrence as possible. Prefer dealing with the error at that point to throwing an exception. Catch any exceptions in the nearest handler that has enough information to deal with them. Do what you can with the exception at the current level; if that doesn't solve the problem, rethrow the exception. If you're using exception specifications, install your own unexpected( ) function using set_unexpected( ). Your unexpected( ) should log the error and rethrow the current exception. That way, if an existing function gets redefined and starts throwing exceptions, it won't abort the program. Create a user-defined terminate( ) (indicating a programmer error) to log the error that caused the exception, then release system resources, and exit the program. If a destructor calls any functions, those functions may throw exceptions. A destructor cannot throw an exception (this can result in a call to terminate( ), which indicates a programming error), so any destructor that calls functions must catch and manage its own exceptions. Don't create your own mangled private data member names, unless you have a lot of pre-existing global values; otherwise, let classes and namespaces do that for you. If you're going to use a loop variable after the end of a for loop, define the variable before the for control expression. This way, you won't have any surprises when implementations change to limit the lifetime of variables defined within for control-expressions to the controlled expression. Watch for overloading. A function should not conditionally execute code based on the value of an argument, default or not. In this case, you should create two or more overloaded functions instead. Hide your pointers inside container classes. Bring them out only when you are going to immediately perform operations on them. Pointers have always been a major source of bugs. When you use new, try to drop the resulting pointer into a container. Prefer that a container own its pointers so it's responsible for cleanup. If you must have a free-standing pointer, always initialize it, preferably to an object address, but to zero if necessary. Set it to zero when you delete it to prevent accidental multiple deletions. Don't overload global new and delete; always do it on a class-by-class basis. Overloading the global versions affects the entire client programmer project, something only the creators of a project should control. When overloading new and delete for classes, don't assume you know the size of the object; someone may be inheriting from you. Use the provided argument. If you do anything special, consider the effect it could have on inheritors. Don't repeat yourself. If a piece of code is recurring in many functions in derived classes, put that code into a single function in the base class and call it from the derived class functions. Not only do you save code space, you provide for easy propagation of changes. This is possible even for pure virtual functions (see Chapter 13). You can use an inline function for efficiency. Sometimes the discovery of this common code will add valuable functionality to your interface. Prevent object slicing. It virtually never makes sense to upcast an object by value. To prevent this, put pure virtual functions in your base class. Sometimes simple aggregation does the job. A passenger comfort system on an airline consists of disconnected elements: seat, air conditioning, video, etc., and yet you need to create many of these in a plane. Do you make private members and build a whole new interface? No - in this case, the components themselves are also part of the public interface, so you should create public member objects. Those objects have their own private implementations, which are still safe. C: Simulating virtual constructors TODO: Incorporate this with the design patterns chapter, as a creational pattern During a constructor call, the virtual mechanism does not operate (early binding occurs). Sometimes this is awkward. In addition, you may want to organize your code so you don't have to select an exact type of constructor when creating an object. The second is much simpler to implement and maintain, but restricts you to creating objects on the heap. It seems logical that inside the constructor for a Shape object, you would want to set everything up and then draw( ) the shape. However, this doesn't work inside the constructor, for the reasons given in Chapter 13: Virtual functions resolve to the local function bodies when called in constructors. If you want to be able to call a virtual function inside the constructor and have it do the right thing, you must use a technique to simulate a virtual constructor. This is a conundrum. Remember the idea of a virtual function is that you send a message to an object and let the object figure out the right thing to do. But a constructor builds an object. It makes sense that a constructor can't be virtual because it is the one function that absolutely must know everything about the type of the object. And yet there are times when you want something approximating the behavior of a virtual constructor. In the Shape example, it would be nice to hand the Shape constructor some specific information in the argument list and let the constructor create a specific type of Shape (a Circle, Square, or Triangle) with no further intervention. Ordinarily, you'd have to make an explicit call to the Circle, Square, or Triangle constructor yourself. Coplien75 calls his solution to this problem envelope and letter classes. The envelope class is the base class, a shell that contains a pointer to an object of the base class. All the function calls are then handled by the base class through its pointer. When you build a virtual constructor scheme, you must exercise special care to ensure this pointer is always initialized to a live object. The type enumeration inside class Shape and the requirement that the constructor for Shape be defined after all the derived classes are two of the restrictions of this method. Each time you derive a new subtype from Shape, you must go back and add the name for that type to the type enumeration. Then you must modify the Shape constructor to handle the new case. The disadvantage is you now have a dependency between the Shape class and all classes derived from it. However, the advantage is that the dependency that normally occurs in the body of the program (and possibly in more than one place, which makes it less maintainable) is isolated inside the class. In addition, this produces an effect like the cheshire cat technique in Chapter 2; in this case all the specific shape class definitions can be hidden inside the implementation files. That way, the base-class interface is truly the only thing the user sees. In this example, the information you must hand the constructor about what type to create is very explicit: It's an enumeration of the type. However, your scheme may use other information - for example, in a parser the output of the scanner may be handed to the virtual constructor, which then uses that text string to determine what exact token to create. The virtual constructor Shape(type) can only be declared inside the class; it cannot be defined until after all the base classes have been declared. However, the default constructor can be defined inside class Shape, but it should be made protected so temporary Shape objects cannot be created. This default constructor is only called by the constructors of derived-class objects. You are forced to explicitly create a default constructor because the compiler will create one for you automatically only if there are no constructors defined. Because you must define Shape(type), you must also define Shape( ). The default constructor in this scheme has at least one very important chore - it must set the value of the S pointer to zero. In the envelope, S is important because it points to the actual object, but in the letter, S is simply excess baggage. Even excess baggage should be initialized, however, and if S is not set to zero by the default constructor called for the letter, bad things happen (as you'll see later). The virtual constructor takes as its argument information that completely determines the type of the object. Notice, though, that this type information isn't read and acted upon until run-time, whereas normally the compiler must know the exact type at compile-time (one other reason this system effectively imitates virtual constructors). As an example, consider the call to draw( ) inside the virtual constructor. If you trace this call (either by hand or with a debugger), you can see that it starts in the draw( ) function in the base class, Shape. This function calls draw( ) for the envelope S pointer to its letter. All types derived from Shape share the same interface, so this virtual call is properly executed, even though it seems to be in the constructor. To understand how it works, consider the code in main( ). Ordinarily in a situation like this, you would call the constructor for the actual type, and the VPTR for that type would be installed in the object. Here, however, the VPTR used in each case is the one for Shape, not the one for the specific Circle, Square, or Triangle. In the for loop where the draw( ) function is called for each Shape, the virtual function call resolves, through the VPTR, to the corresponding type. However, this is Shape in each case. In fact, you might wonder why draw( ) was made virtual at all. Thus the run-time cost of using virtual constructors is one more virtual call every time you make a virtual function call. A remaining conundrum In the virtual constructor scheme presented here, calls made to virtual functions inside destructors will be resolved in the normal way, at compile-time. You might think that you can get around this restriction by cleverly calling the base-class version of the virtual function inside the destructor. Unfortunately, this results in disaster. The base-class version of the function is indeed called. For the letter, S is zero, set by the protected default constructor. Of course, you could prevent this by adding even more code to each virtual function in the base class to check that S is not zero. Or, you can follow two rules when using this virtual constructor scheme: 1. Never explicitly call root class virtual functions from derived-class functions. 2. Always redefine virtual functions defined in the root class. The second rule results from the same problem as rule one. If you call a virtual function inside a letter, the function gets the letter this pointer. If the virtual call resolves to the base-class version, which will happen if the function was never redefined, then a call will be made through the letter this pointer to the letter S, which is again zero. You can see that there are costs, restrictions, and dangers to using this method, which is why you don't want to have it as part of the programming environment all the time. It's one of those features that's very useful for solving certain types of problems, but it isn't something you want to cope with all the time. Fortunately, C++ allows you to put it in when you need it, but the standard way is the safest and, in the end, easiest. Destructor operation The activities of destruction in this scheme are also tricky. To understand, let's verbally walk through what happens when you call delete for a pointer to a Shape object - specifically, a Square - created on the heap. Normally, you might say that it's a virtual call, so Square's destructor will be called. But with this virtual constructor scheme, the compiler is creating actual Shape objects, even though the constructor initializes the letter pointer to a specific type of Shape. The virtual mechanism is used, but the VPTR inside the Shape object is Shape's VPTR, not Square's. This resolves to Shape's destructor, which calls delete for the letter pointer S, which actually points to a Square object. This is again a virtual call, but this time it resolves to Square's destructor. With a destructor, however, all destructors in the hierarchy must be called. Square's destructor is called first, followed by any intermediate destructors, in order, until finally the base-class destructor is called. This base-class destructor has code that says delete S. When this destructor was called originally, it was for the envelope S, but now it's for the letter S, which is there because the letter was inherited from the envelope, and not because it contains anything. So this call to delete should do nothing. The solution to the problem is to make the letter S pointer zero. Then when the letter base-class destructor is called, you get delete 0, which by definition does nothing. Because the default constructor is protected, it will be called only during the construction of a letter, so that's the only situation where S is set to zero. A simpler alternative This virtual constructor scheme is unnecessarily complicated for most needs. If you can restrict yourself to objects created on the heap, things can be made a lot simpler. All you really want is some function (which I'll call an object-maker function) into which you can throw a bunch of information and it will produce the right object. This function doesn't have to be a constructor if it's OK to produce a pointer to the base type rather than an object itself. The only restrictions are that the enum in the base class must be changed every time you derive a new class (true also with the envelope-and-letter approach) and that objects must be created on the heap. This latter restriction is enforced by making all the constructors protected, so the user can't create an object of the class, but the derived-class constructors can still access the base-class constructors during object creation. This is an excellent example of where the protected keyword is essential. D: Recommended reading General topics The C++ Programming Language, 3rd edition, by Bjarne Stroustrup (Addison-Wesley 1997). To some degree, the goal of the book that you're currently holding is to allow you to use Bjarne's book as a reference. Since his book contains the description of the language by the author of that language, it's typically the place where you'll go to resolve any uncertainties about what C++ is or isn't supposed to do. When you get the knack of the language and are ready to get serious, you'll need it. Legality of use? Can this be put on the CD Rom?). Effective C++ and More Effective C++, by Scott Meyers. Large Scale C++ (?) by John Lakos. C and C++ Code Capsules by Chuck Allison. Ruminations on C++ by Koenig and Moo. C++ Gems, Stan Lippman, editor. SIGS publications. The Design and Evolution of C++, by Bjarne Stroustrup My own list of books Not all of these are currently available. Sullivan) Sender: tdsulli@raptor.sandia.gov To: eckel@aol.com Bruce, I have downloaded the May?? revision of code from oak.oakland.edu. I believe that there are some logical errors in some of the code. The code compiles okay but might not produce the desired results. Ever otherone stored will be skipped. But code will compile and run! As I progress in the class I will be on the lookout for more such ERRORS. Unique Features of C++ Functions C++ functions have a number of improvements over C functions, designed to make them easier to program and use. Inline Functions The preprocessor macro function introduced earlier in this chapter for the MATHOPS program saves typing, improves readability, reduces errors and eliminates the overhead of a function call. Preprocessor macro functions are popular in C, but they have the drawback that they are not real functions, so the usual error checking doesn't occur during compilation. C++ encourages (sometimes even requires) the use of small functions. The programmer concerned with speed, however, might opt to use preprocessor macros rather than functions to avoid the overhead of a function call. To eliminate the cost of calls to small functions, C++ has inline functions. When the compiler encounters an inline definition, it doesn't generate code as it does with an ordinary function definition. Instead, it remembers the code for the function. In an inline function call, (which looks like a call to any other function), the compiler checks for proper usage as it does with any function call, then substitutes the code for the function call. Thus, the efficiency of preprocessor macros is combined with the error-checking of ordinary functions. The inline function is another tough nut when it comes to terminology. Because the body of the function doesn't actually reserve any storage for the function code, it is tempting to call it a declaration rather than a definition. Indeed, you cannot declare an inline function in the usual sense. In the declaration: inline int one( ); the inline keyword has no effect - it does the compiler no good to know that a function is an inline if it doesn't have the code to substitute when it encounters a function call. Generally, this is accomplished by putting the inline function definition in a header file. There is nothing else that could be called a definition other than the place where the function body is, so it is called a definition. Saving space Because an inline function duplicates the code for every function call, you might think it automatically increases code space. For small functions (which inlines were designed for) this isn't necessarily true. Keep in mind that a function call requires code to pass arguments and to handle the return value; this code isn't present for an inline. If your inline function turns out to be smaller than the amount of code necessary for arguments and the return value, you are actually saving space. In addition, if the inline function is never called, no code is ever generated. With an ordinary function, code for that function is there (only once) whether you call it or not. The inline keyword is actually just a hint to the compiler. The compiler may ignore the inline and simply generate code for the function someplace. The code is often clearer to the reader, as well. The result is often an abuse of inline functions; they are used because they are easier and clearer rather than because they are faster. This abuse is most rampant in (of all places) articles and books on programming in C++. As you will see, some projects in this book push the boundaries of good sense when using inlines. You may wonder what the problem is. The C++ compiler must remember the definition for the inline function, rather than simply compiling it and moving on as with an ordinary function. The speed benefits of inline functions tend to diminish as the function grows in size. At some point the overhead of the function call becomes small compared to the execution of the function body, and the benefit is lost. C++ function overloading C++ introduces the concept of function overloading. This means you can call the same function name in a variety of ways, depending on your needs. An overloaded print( ) function might be able to handle floats, ints and strings: print(3.14); print(47); print(this is a string); Here, the function name print is overloaded with several different meanings. The most useful place to overload functions is in classes, as we shall see later. You can also overload ordinary functions by using the overload keyword in earlier releases of the language. The keyword is still available, but obsolete, in modern releases of C++. You may still see it in old code, but you should never use it. Pointers are discussed in detail in Chapter 4, but they are such an integral part of C and C++ that a brief introduction is necessary here. A pointer is a variable that holds the address of another variable. Because you don't know how long a string is at compile time, the compiler can't know how to pass the string to the print( ) function. If we tell print( ) where the string lives by passing the address, the function can figure out for itself where to get the characters in the string and how long the string is (at run-time, instead of compile-time). Distinguishing overloaded functions For the compiler to tell the difference between one use of the function and another, each time the function is overloaded it must have a unique set of arguments. Improvements to overloading are described in Chapter 11. Is overloading object-oriented? Object-Oriented programming can be perceived as one more step in the long process of shifting the petty details of managing a program from the programmer onto the computer. Function overloading allows you to use the same message name with different arguments and the compiler figures out how to handle it. You don't have to remember as many message names - you do less work, the computer does more work, so it's object-oriented, right? It depends. Much of the history of object-oriented programming happened in an interpreted environment, where all messages are resolved during program execution. Resolving messages at compile-time rather than run-time is not considered an object-oriented feature if you come from this background. Resolving all messages at run-time introduces a lot of overhead to the system. In addition, the compiler can't do static type-checking (and error detection). Both these drawbacks are counter to C++'s design philosophy. Whether function overloading is object-oriented really depends upon where you draw the boundary. If you are willing to be casual and say I write the code and the computer takes care of it. I don't care how then function overloading is object-oriented. If you insist that all messages must be resolved at run-time then function overloading (as well as many other implementation details of C++) isn't object-oriented. Default arguments C++ functions may have default arguments, which are substituted by the compiler if you don't supply your own. Default arguments are specified in the function declaration: void foo(int i = 0); You can now call the function as foo( ) (which is the same as foo(0)) or foo(47). Default arguments seem like function overloading to the client programmer. Note that the variable name i is optional in the declaration, even with default arguments. Pre-defined classes were used in the last chapter, and now you can start defining your own classes. A class is a way to package associated pieces of data together with functions that operate on that data. It allows you to hide data and functions, if desired, from the general purview. When you create a class, you are creating a new type of data (an abstract data type) and the operations for that type. It is a data type like a float is a data type. When you add two floats, the compiler knows what to do. A class definition teaches the compiler what to do with your new data type. A class definition consists of the name of the class followed by a body, enclosed in braces, followed by a semicolon (remember the semicolon -leaving it off causes strange errors). The class body contains variable definitions and function declarations. These variables and functions are an intimate part of the class, only used in association with an object belonging to that class. Although the variable definitions look like the ordinary definitions of local variables inside a function, no storage is allocated for them until a variable (object) of the class type is created. When this happens, all the storage for the variables is allocated at once, in a clump. The variables and functions (collectively called members) of a class are normally hidden from the outside world - the user cannot access them. These variables and functions are called private. You make the privacy explicit with the private keyword; members in a class default to private. To allow the client programmer access to members, use the public keyword. You can only change the value of i by calling the member function set( ); you can only read it by calling the member function read( ). It is important to remember that only member functions (and friend functions, described later) may read or change the values of private variables. As you can see, set( ) and read( ) are inline functions, but the inline keyword isn't used! Because a class is so unique, the compiler doesn't need any hints to know that a function is inline. You can also overload functions inside a class without using the overload keyword (you've always been able to do this, but overload is now obsolete). Thinking about objects You can think of an object as an entity with an internal state and external operations. The external operations in C++ are member functions. The functions that execute the messages in an object-oriented language are called methods. Messages are the actual function calls. The concept of state means an object remembers things about itself when you are not using it. An ordinary C function (one without any static variables) is stateless because it always starts at the same point whenever you use it. Since an object has a state, however, you can have a function that does something different each time you call it. Design benefits One of the design benefits of C++ is that it separates the interface from the implementation. The interface in C++ is the class definition. The interface says: here's what an object looks like, and here are the methods for the object. It doesn't specify (except in the case of inline functions) how the methods work. The implementation shows how the methods work, and consists of all the member function definitions. While the interface must have been seen by the compiler anyplace you use the class, the implementation can only exist in one spot. If, at some point in the future, the programmer wishes to improve the implementation, it doesn't disturb the interface or all the code compiled using the interface. The implementation can be changed, and the whole system re-linked (only the implementation code must be re-compiled). Assuming the interface is well-planned, code changes are very isolated, which prevents the propagation of bugs. In a similar vein, you can design and code the interface and delay writing the implementation code. The interface is used as if the implementation code exists (only the linker knows for sure). This means you can make the equivalent of a rough sketch of your system and check to see that everything fits together properly by compiling all the modules that use the interface. Declaration vs. It can be argued that a class description reserves no storage (except in the case of static members) and it is really just a model of a new data type and not an actual variable, so it should be called a declaration. The common terminology is as follows. A class name without a description of the class, such as: class NatureBoy; will be called a name declaration. If you choose to assign a value when reserving storage for the variable, the compiler does that too. In effect, the compiler constructs the variable for you. When a variable of a built-in type goes out of scope, the compiler cleans up the storage for that variable by freeing it, in effect, it destroys the variable. C++ makes user-defined types (classes) as indistinguishable as possible from built-in types. This means the compiler needs a function to call when the variable is created (a constructor) and a function to call when the variable goes out of scope (a destructor). If the programmer doesn't supply constructors (there can be more than one overloaded constructor) and a destructor (there can only be one) for a class, the compiler assumes the simplest actions. The constructor is a member function with the same name as the class. The constructor assumes that the storage has been allocated for all the variables in the object's structure when it is called. The print( ) member function displays the private values of the objects after they are initialized. The name of the destructor is the class name with a tilde attached at the beginning. For the above example, the destructor name would be ~ThizBin( ). The destructor never takes any arguments; it is only called by the compiler and cannot be called explicitly by the programmer (Except for one unusal situation, used when you place an object at a specific location in memory. See explicit destructor calls in the index). While you will almost always want to perform various types of initialization on an object, the default destructor (doing nothing) is often sufficient and you may not need to define a destructor. However, if your object initializes some hardware (e.g.: puts a window up on the screen) or changes some global value, you may need to undo the effect of the object (e.g.: close the window) when the object is destroyed. For this, you need a destructor. Notice that after the first group of variables is created, F is created, then destroyed, and G is created, then destroyed, then the rest of the variables are destroyed. When the closing brace of a scope is encountered, destructors are called for each variable in the scope. It is possible, however, to define a variable in a class such that only one instance of the variable is created for all the objects ever defined for that class. Each object has access to this one piece of data, but the data is shared among all the objects instead of being duplicated for each object. To achieve this effect, declare the variable static (A third meaning of the keyword static). You often use static member variables to communicate between objects. For a more sophisticated example of this, look at the examples, see reference counting in the index. You must explicitly reserve storage for, and initialize, all static objects. Storage isn't created for you, since only one piece of storage is needed for the whole program. This repeats the type of the object but uses the class name and the scope resolution operator with the identifier. Other than that, it's the same as an ordinary global object definition: int Bad::i = 33; This definition and initialization occurs outside of all class and function bodies. That is, storage is always allocated for a const data member, so a const occupies space inside a class. The other rules of C++ still apply - in particular, a const must be initialized at the point it is defined. What does this mean, in the case of a class? Storage isn't allocated for a variable until an object is created, and that is the point where the const must be initialized. Therefore, the meaning of const for class members is constant for that object, for its lifetime. The initialization of a const must happen in the constructor. It is a special action, and must happen in a special way, so that its value is guaranteed to be set at all times. This is performed in the constructor initializer list, which occurs after the constructor's argument list but before its body, to indicate that the code is executed before the constructor body is entered. Notice that it looks like a constructor call. It is indeed intended to mimic a constructor call, but the meaning of this syntax for built-in types in the constructor initializer list is simply assignment. Fortunately, there is a convenient workaround for this problem. The enumerated data type enum (described later in this chapter) is designed to associate names with integral numbers. Normally enum is used to distinguish a set of names by letting the compiler automatically assign numbers to them. Storage is never allocated for enumeration names, so the compiler always has the values available. Defining class member functions All the member function definitions so far have been inline. In the general case, functions will be defined in a separate code file. This section shows the specifics of defining member functions. The scope resolution operator :: To define a member function, you must first tell the compiler that the function you are defining is associated with a particular class. This is accomplished using the scope resolution operator (::). The functions will now be compiled as normal functions instead of inline functions. Use the scope resolution operator any time you are not sure which definition the compiler will use. You can also use scope resolution to select a definition other than the normal default. If the scope resolution operator is used with no name preceding it, it means use the global name. Calling other member functions As the example above implies, you can call member functions from inside other member functions. It was stated earlier that a member function can never be called unless it is associated with an object, so this might look a bit confusing at first. If you are defining a member function, that function is already associated with an object (the current object, also referred to with the keyword this). A member function can be called by simply using its name inside another member function (no object name and dot is necessary inside a member function). To illustrate, here's an example that creates a smart array (one that checks boundaries). Whenever the user wants to set or read a value, check_index( ) is called first to make sure the array boundaries are not exceeded. You can see that C and C++ try to make the definition of a variable mimic its use (this doesn't always succeed). Remember that elements are counted from zero, so if you define an array with 100 elements you must start at element 0 and stop at element 99. You can't always make everything fit neatly into one class; sometimes other functions must have access to private elements of your class for everything to work together harmoniously. You could make some elements public, but this is a bad idea unless you really want the client programmer to change the data. The solution in C++ is to create friend functions. These are functions that are not class members (although they can be members of some other class; in fact, an entire class can be declared a friend). A friend has the same access privileges as a member function, but it isn't associated with an object of the host class (so you can't call member functions of the host class without associating the functions with objects). The host class has control over granting friend privileges to other functions, so you always know who has the ability to change your private data (it's much easier to trace bugs that way). In a non-friend function, the references to objA.time and objB.time would be illegal. References Something is introduced in this example: the 'and' in the argument list for synchronize( ). Normally, when you pass an argument to a function, the variable you specify in the argument list is copied and handed to the function. If you change something in the copy, it has no effect on the original. When the function ends, the copy goes out of scope and the original is untouched. If you want to change the original variable, you must tell the function where the original variable lives instead of making a copy of the original variable. As described earlier in this chapter, a pointer is one way of telling a function where the original variable lives. In that example, the address of a string was passed to a function called print(char *). It was necessary to use the address because the compiler couldn't know how long the string was. A reference, specified by the operator and, is the second way to pass an address. It is a much nicer way to pass an address to a function, and it is only available in C++. A reference quietly takes the address of an object. Inside the function, the reference lets you treat the name as if it were a real variable, and not just the address of a variable. As you can see in the definition for synchronize( ), the elements of objA and objB are selected using the dot, just as if objA and objB were objects, and not addresses of objects. The compiler takes care of everything else. References are described in detail in Chapter 4. Notice that synchronize( ) can reach right in and modify the private elements of both objA and objB. This is only true because synchronize( ) was declared a friend of both classes. Notice that synchronize( ) only takes one argument here, since a member function already knows about the object it is called for. Also notice that the name declaration for MicrowaveOven is unnecessary before class Watch, since it is included in the friend declaration. Often, the choice of whether to use member functions or non-member functions comes down to your preference for the way the syntax should look. Declaring a friend member function It is also possible to select a single member function from another class to be a friend. Here, however, the compiler must see everything in the right order. Each of these constructs has a different purpose. They include the plain structure struct, the enumerated data type enum and the space-saving union. One effect of a static data member is that it doesn't occupy space in each object, so the size of each object is reduced. Although member functions don't occupy space in an object, when a function is called that function must somehow know which object data it is accessing. This is done by the compiler, secretly, by passing the starting address of the object into the member function. You can access the starting address while inside the member function using the keyword this. The extra overhead of the member function call when passing this is analogous to the extra size in an object when adding data members. In line with this analogy, you can remove the extra time involved in a member function call by making the member function static. Like a static data member, a static member function acts for the class as a whole, not for a particular object of the class. The starting address of the object (this) is not passed to a static member function, so it cannot access non-static data members (and the compiler will give you an error if you try). The only data members which can be accessed by a static member function are static data members. You can call a static member function in the ordinary way, with the dot or the arrow, in association with an object. That function will have the faster calling time of an ordinary, global function but its name will be visible only within the class, so it won't clash with global function names. This is accomplished with the keywords const and volatile. You will see const member functions used far more often than volatile member functions, but the syntax works the same way. This applies to variables of built-in types, as you've seen. When the compiler sees a const like this, it stores the value in its symbol table and inserts it directly, after performing type-checking (remember that this is an improvement in C++, which acts differently than C). In addition, it prevents you from changing the value of a const. The reason to declare an object of a built-in type as const is so the compiler will insure that it isn't changed. You can also tell the compiler that an object of a user-defined type is a const. However, the other aspect of a const - that it cannot be changed during its lifetime - is still valid and can be enforced. The concept of constness can also be applied to an object of a user-defined type, and it meanst the same thing: the internal state of a const object cannot be changes. This can only be enforced if there is some way to insure that all operations performed on a const object won't it. C++ provides a special syntax to tell the compiler that a member function doesn't change an object. The keyword const placed after the argument list of a member function tells the compiler that this member function can only read data members, but it cannot write them. Creating a const member function is actually a contract which the compiler enforces. However, the compiler also insures that the function definition conforms to the const specifier. Notice that the const keyword must be used in both the declaration and the definition. Also, the compiler will verify that a function doesn't modify any data members if you say it's a const, whether or not that function is defined inline. Therefore, for the greatest flexibility of your classes, you should declare functions as const when possible, since you never know when the user might want to call such a function for a const object. This practice will be followed in this book. Casting away const-ness In some rare cases you may wish to modify certain members of an object, even if the object is a const. That is, you may want to leave the const on all the members except a select few. You can do this with a rather odd-looking cast. Remember that a cast tells the compiler to suspend its normal assumptions and to let you take over the type-checking. Thus it is inherently dangerous and not something you want to do casually. However, the need sometimes occurs. To cast away the const-ness of an object, you select a member with the this pointer. Since this is just the address of the current object, this seems redundant. However, by preceeding this with a cast to itself, you implicitly remove the const (because const isn't part of the cast). Of course, this isn't the most straightforward code in the world, but when you do this kind of thing you're intentionally breaking the type-safety mechanism, and that is usually an ugly process. You should know what it looks like, but you should try not to do it. For example, in a data communication program or alarm system, some piece of hardware may cause a change to a variable, while the program itself may never change the variable. The reason it's important to be able to declare a variable volatile is to prevent the compiler from making assumptions about code associated with that variable. The primary concern here is optimizations. If you read a variable, and (without changing it) read it again sometime later, the optimizer may assume that the variable hasn't changed and delete the second read. If the variable was declared volatile, however, the optimizer won't touch any code associated with the variable. The syntax for const and volatile member functions is identical. Only volatile member functions may be called for volatile objects. Since port is both const and volatile, it can only be read and the compiler won't optimize away any reads of that location. In practice, however, volatile is used far less frequently than const. Debugging hints When you're writing your own classes, you can use the features of C++ to your advantage and build in debugging tools. In particular, each class should have a function called dump( ) (or some similar name) that will display the contents of an object. This way you can dump( ) your objects at various points in your program to trace their progress. If you build the dump( ) function in from the start, you won't have as much mental resistance to running a trace. In this program, the variable counter is normally completely hidden from the user's view, and no functions are provided to access it. When debugging, this information may be essential. It is best to provide as much information as possible, as well as optional messages, in the dump( ) function. 1 Bjarne Stroustrup, The C++ Programming Language, Addison-Wesley, 1986 (first edition). 2 Using C++, ibid. 3 Using C++ and C++ Inside and Out, ibid. 4 Ibid. 5 Ibid. 6 See Multiparadigm Programming in Leda by Timothy Budd (Addison-Wesley 1995). 7 Some people make a distinction, stating that type determines the interface while class is a particular implementation of that interface. 8 I'm indebted to my friend Scott Meyers for this term. 9 This uses the Unified Notation, which will primarily be used in this book. 11 See Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma et al., Addison-Wesley, 1995. 12 A reference to vampires made in The Mythical Man-Month, by Fred Brooks, Addison-Wesley, 1975. 13 Another good perspective book is Object Lessons by Tom Love, SIGS Books, 1993. 14 Through Two Arts, Inc., 12021 Wilshire Blvd. Suite 868, Los Angeles, CA 90025. 15 My rule of thumb for estimating such projects: If there's more than one wild card, don't even try to plan how long it's going to take or how much it will cost. There are too many degrees of freedom. 16 The Mythical Man-Month, ibid. 17 My observations here are based on what I am most familiar with: the extensive capabilities of Microsoft Word, which was used to produce the camera-ready pages of this book. 18 I encourage the choice of one that uses simple boxes, lines, and symbols that are available in the drawing package of the word processor, rather than amorphous shapes that are difficult to produce. 19 The best introduction is still Grady Booch's Object-Oriented Design with Applications, 2nd edition, Wiley and Sons 1996. His insights are clear and his prose is straightforward, although his notations are needlessly complex for most designs. The trouble with rapid prototyping is that people didn't throw away the prototype, but instead built upon it. Combined with the lack of structure in procedural programming, this often leads to messy systems that are expensive to maintain. 21 These are summarized in Object Analysis and Design: Description of Methods, edited by Andrew T.F. Hutt of the Object Management Group (OMG), John Wiley and Sons, 1994. A more recent edition focuses on C++. 23 See Designing Object-Oriented Software by Rebecca Wirfs-Brock et al., Prentice Hall, 1990. 24 See Object-Oriented Modeling and Design by James Rumbaugh et al., Prentice Hall, 1991. 25 See Gamma et al., ibid. 26 Note that all conventions seem to end after the agreement that some sort of indentation take place. The feud between styles of code formatting is unending. 27 See Josée Lajoie, The new cast notation and the bool data type, C++ Report, September 1994. 28 You should be aware that this term seems to be the subject of ongoing debate. Some people use it as defined here; others use it to describe implementation hiding, discussed in Chapter 2. 29 This name is attributed to John Carolan, one of the early pioneers in C++, and of course, Lewis Carroll. 30 Chapter 11 demonstrates a much better way to create an object on the heap with new. 31Actually, there's a syntax that does allow you to do this. But it's for special cases and doesn't solve the general problem described here. 32Andrew Koenig goes into more detail in his book C Traps and Pitfalls (Addison-Wesley, 1989). 33 Co-author with Tom Plum of C++ Programming Guidelines, Plum Hall, 1991. 34 Your compiler may not have implemented this feature yet; check your local documentation. 35 Your compiler may not have implemented this feature yet; check your local documentation. 36Bjarne Stroustrup and Margaret Ellis, The Annotated C++ Reference Manual, Addison-Wesley, 1990, pp. 20-21. 37 Thanks to Owen Mortensen for this example 38 Rob Murray, C++ Strategies and Tactics, Addison-Wesley, 1993, page 47. 39 At the time of this writing, explicit was a new keyword in the language. Your compiler may not support it yet. 40Smalltalk, for instance, uses this approach with great success. 41At Bell labs, where C++ was invented, there are a lot of C programmers. Making them all more efficient, even just a bit, saves the company many millions. 42Actually, not all pointers are the same size on all machines. In the context of this discussion, however, they can be considered to be the same. 43 The OOPS library, by Keith Gorlen while he was at NIH. Generally available from public sources. 44 We'll probably never know the full story because control of the language was still within ATandT at the time. 45 The C++ Programming Language by Bjarne Stroustrup (1st edition, Addison-Wesley, 1986). 46 The inspiration for templates appears to be ADA generics. 48 A reference to the British animated short The Wrong Trousers by Nick Park. 49 Check your compiler version information to see if it supports member function templates. 50 See, for example, Rogue Wave, which has a well-designed set of C++ tools for all platforms. 51 Available at this writing in draft form only. 52 The material in this chapter was originally created by Nancy Nicolaisen 53 The implementation and test files for FULLWRAP are available in the freely distributed source code for this book. See preface for details. 54 Newer implementations of iostreams will still support this style of handling errors, but in some cases will also throw exceptions. 55 Note the name has been truncated to handle the DOS limitation on file names. You may need to adjust the header file name if your system supports longer file names (or simply copy the header file). 56 These only appear in the revised library; you won't find them in older implementations of iostreams. 57 Before putting nl into a header file, you should make it an inline function (see Chapter 7). 58 In a private conversation. 59 Contributed to the C++ Standard by Alexander Stepanov and Meng Lee at Hewlett-Packard. 60 These were actually created to abstract the locale facets away from iostreams, so that locale facets could operate on any sequence of characters, not only iostreams. Locales allow iostreams to easily handle culturally-different formatting (such as representation of money), and are beyond the scope of this book. 61 This is another example suggested by Nathan Myers. 62 See also Jan Gray, C++ Under the Hood, a chapter in Black Belt C++ (edited by Bruce Eckel, MandT Press, 1995). 63 For easy readability the code was generated for a small-model Intel processor. 64 Sometimes there's only a single function for streaming, and the argument contains information about whether you're reading or writing. 65 A phrase coined by Zack Urlocker. 66 You may be surprised when you run the example - some C++ compilers have extended longjmp( ) to clean up objects on the stack. This is nonportable behavior. 67 See Josée Lajoie , The new cast notation and the bool data type, C++ Report, September, 1994 pp. 46-51. 68 For this particular compiler. Yours will probably be different. 71 Free Web servers are relatively common and can be found by browsing the Internet; Apache, for example, is the most popular Web server on the Internet. 72 GNU stands for Gnu's Not Unix. The project, created by the Free Software Foundation, was originally intended to replace the Unix operating system with a free version of that OS. Linux appears to have replaced this initiative, but the GNU tools have played an integral part in the development of Linux, which comes packaged with many GNU components. 73 This appendix was suggested by Andrew Binstock, editor of Unix Review, as an article for that magazine. 74 Explained to me by Andrew Koenig. 75James O. Coplien, Advanced C++ Programming Styles and Idioms, Addison-Wesley, 1992. The questions will be repeated here, along with the answers. What is object oriented programming, in one sentence? A: Sending messages to objects 2. 2. In C++, how is the C language extended to embody this primary concept of object-oriented programming? A: Functions are added to structs, so structures describe not only an agglomeration of data, but an agglomeration of data and functions. We often say this means that a structure has characteristics (described by the data) and behaviors (described by the functions). 3. 3. What is the difference between a structure and an object? A: A structure is a description of a type, and an object is a variable of that new type. 4. 4. Can you compile C code with C++? 5. 5. Does a structure occupy storage? Do the functions in a structure require storage for each function, in each object? A: No. A member function is like an ordinary function except for its name, so there's only one piece of storage allocated for that function regardless of how many variables you create. The only difference is that the name is scoped within the name of the structure. 6. 6. What are the two basic differences between an ordinary C function and a member function (inside a structure) in C++? A: The first is that name is scoped within the structure so it's no longer a global name, and the second is that the address of the structure is secretly passed in as the first argument. Other than that, it's just like an ordinary global function. 7. 7. In a function definition, How do you tell the compiler this is a member function of a particular class, and not an ordinary function definition. A: With the scope resolution operator :: 8. 8. What is a translation unit? A: A single file which generates code. Generally, these have a file name extension of.CPP, to distinguish them from header files (with extensions of .H). The compiler is invoked once for each CPP file to create an object module (this name is an artifact, it is the objective of the compilation rather than anything object-oriented). 9. 9. What does the linker do? A: The primary job of the linker is to resolve references between function calls. These references often span a group of translation units and may also include various libraries (a file that often has the extension LIB), which are collections of OBJ files. The linker takes all these and binds them into an executable which contains the necessary code to resolve the function calls. How does the linker tell the difference between an ordinary C function call, and a call to a member function which happens to have the same name? A: When the compiler generates internal names, it does something which is often referred to as name mangling. Therefore the linker cannot confuse the two, and neither can the compiler. How does a member function know which object it is supposed to be working on? That is, what syntax do you use if you have an object and want to call a member function for that object, rather than for another object? A: Just like in C, if you have a structure and you want to select a member of that structure, you give the name of the variable, followed by the '.' (dot) operator, followed by the name of the member function. Of course, since it's a function, it is followed by the argument list. What syntax do you use if you have a pointer to a structure, and you want to call a member function? Again, since it's a member function you must use the argument list. Also, since it's a function it can have a return value. What kind of code does the compiler generate when you call a member function? That is, how is the information passed into the function about what object is called? A: The information about what object is called is secretly passed in as the first argument of the function. Also, when you call the member function for a particular object, the compiler takes the address of that object and passes that address as the first argument to the member function. All this happens quietly. You can think of this whole process as the compiler generating some extra code for you so you can use a more convenient syntax. What is the name of the address of the object, referred to from inside the member function? A: The name the keyword this. How do you call a member function from within another member function? A: You simply call it. Since you're inside a member function of that class, the compiler first looks for another member function of the same class before it goes looking for a global function of the same name. Suppose you have a class, and you're inside a function f(), and there's a member function of the same class called g(), and there's also a global function g(). How do you call the global function g() so the member function isn't automatically called instead. A: Use the global scope-resolution operator, that is the scope-resolution operator with nothing preceding the operator. This refers to the global version of a function. How does the compiler insure the consistency of structures across translation units? A: The compiler implicitly forces you to put structure declarations (that is, descriptions of the structures) inside header files. The compiler must see the description of the structure before you can use that class by either declaring a variable of that class (that is, an object), or call a member function for that variable. In addition, the compiler complains if it sees a multiple declaration. Therefore, the intelligent thing to do is put the declaration of a class into a single header file, and put the definitions into a single CPP file. Then everywhere you use that class you must include the header file. In C, you must use typedef if you want to be able to treat a struct name as if it were a built-in type. This is a common practice in C programming, especially when creating libraries. Is this necessary in C++? How does C++ treat the same situation? A: The C++ compiler automatically creates the equivalent of a typedef (although it generally treats it differently internally). You can then treat the structure name as if it were a true native type. Why does the compiler only allow you to declare a structure once during the compilation of a particular translation unit? A: If it saw the declaration more than once, it wouldn't know which was the correct declaration. Thus, it prevents you from redeclaring a class. It's possible with multiple levels of #includes that a class can be declared more than once. What is the common practice used to prevent the accidental multiple declaration of a class? A: The preprocessor is ordinarily used by creating a mangled name of your own, generally by using the name of the file and the extension, and mixing in underscores. Leading underscores are not used because those are reserved in the Standard C library. How does the combination of header files and the requirement that you must declare header files before they are used increase the safety of your programs? A: C allows you to call functions without first declaring them. This way, the compiler can insure the function is called properly including proper type checking on the arguments. Since the information wasn't there in C, it was possible to make a mis-call which was a very subtle and difficult error to detect. In C++ you get an error message. What is the difference between a declaration and a definition? A: 23. What should you never put into a header file? A: Although there are a few semi-exceptions (which have their own sensibility, and which you'll see throughout the book), you should never put anything into a header file which causes the compiler to allocate storage. Put another way, only declarations should go into header files, never definitions. The linker must never see multiple definitions for the same piece of storage. If you put a function definition, or an object definition into a header file, then that storage will be allocated anywhere the header file is included. There must only be a single definition for a particular function or variable. When the C++ compiler sees a structure which was originally written for a C compiler, what does it do differently than what the C compiler does? A: The only difference is the implied typedef performed for the structure's tagname. Why is the semicolon at the end of a structure declaration so important? A: The semicolon indicates the end of all object definitions for this particular structure. If the semicolon isn't there, the next name the compiler sees it will assume is the definition of an object of that structure type. What's the difference between C and C++ prototypes? Describe the effects of the three access specifiers 29. At what point in the compile-edit-link-run cycle is it determined whether access is legal or not? What effect do access specifiers have on run-time behavior? What effect do access specifiers have on the physical layout of an object? What is the difference between a class and a struct? What is the difference in meaning between the terms data hiding and encapsulation? How does encapsulation help your programming? What are the safety issues? If class is so similar to struct, why was the new keyword created? What does friend mean? Suppose you want to introduce a structure name without its full body. How do you do this? A: This is done with a name declaration. What happens when you define an object? Why is it important in C++ that an object be defined anywhere within a scope, and not just at the beginning? At what point does the compiler allocate storage for the objects in a block? Describe how you would write a program to prove this. What does the compiler do if it can't find a constructor which matches one that you've given it? Does the compiler always call a constructor for an object? Since constructors are so important, are they created automatically? Do they do anything extra which you haven't told them to do? From this chapter, the answer to both questions would be no. But it turns out that special types of constructors may be created for you automatically, and certain activities are secretly installed in the constructor. But those are special behaviors, and their understanding must wait until a future chapter. Why is function overloading absolutely necessary in C++? A: Because objects must be initialized through constructors, which have the same name as the class. You may need to initialize an object in more than one way, so you must be able to overload the constructor by using it's name more than once. How do you prevent global names from being mangled? That is, how do you bring global names in so you can use existing C libraries without worrying that their names will get mangled, and the linker won't be able to find them as a result? What is the difference between function overloading and default arguments? Now you can easily create complex programs. Probably make constructor for objects private and use a maker function to force all objects on the heap. Thinking in Java Bruce Eckel Comments from readers: Much better than any other Java book I've seen. IMHO, an ideal book for studying Java. Anatoly Vorobey, Technion University, Haifa, Israel One of the absolutely best programming tutorials I've seen for any language. Joakim Ziegler, FIX sysop Thank you for your wonderful, wonderful book on Java. Gavin Pillay, Registrar, King Edward VIII Hospital, South Africa Thank you again for your awesome book. I was really floundering (being a non-C programmer), but your book has brought me up to speed as fast as I could read it. It's really cool to be able to understand the underlying principles and concepts from the start, rather than having to try to build that conceptual model through trial and error. Hopefully I will be able to attend your seminar in the not-too-distant future. Randall R. Hawley, Automation Technician, Eli Lilly and Co. The best computer book writing I have seen. Tom Holland This is one of the best books I've read about a programming language... Chapter 16 on design patterns is one of the most interesting things I've read in a long time. Ilan Finci, graduate student and teaching assistant, Institute of Computer Science, The Hebrew University of Jerusalem, Israel The best book ever written on Java. Ravindra Pai, Oracle Corporation, SUNOS product line This is the best book on Java that I have ever found! You have done a great job. Your depth is amazing. I will be purchasing the book when it is published. I have been learning Java since October 96. I have read a few books, and consider yours a MUST READ. These past few months we have been focused on a product written entirely in Java. Your book has helped solidify topics I was shaky on and has expanded my knowledge base. I have even used some of your explanations as information in interviewing contractors to help our team. I have found how much Java knowledge they have by asking them about things I have learned from reading your book (e.g. the difference between arrays and Vectors). Your book is great! Steve Wilkinson, Senior Staff Specialist, MCI Telecommunications Great book. Best book on Java I have seen so far. Jeff Sinclair, Software Engineer, Kestral Computing Thank you for Thinking in Java. It's time someone went beyond mere language description to a thoughtful, penetrating analytic tutorial that doesn't kowtow to The Manufacturers. I've read almost all the others-only yours and Patrick Winston's have found a place in my heart. I'm already recommending it to customers. Thanks again. Richard Brooks, Java Consultant, Sun Professional Services, Dallas Other books cover the WHAT of Java (describing the syntax and the libraries) or the HOW of Java (practical programming examples). Thinking in Java is the only book I know that explains the WHY of Java; why it was designed the way it was, why it works the way it does, why it sometimes doesn't work, why it's better than C++, why it's not. Although it also does a good job of teaching the what and how of the language, Thinking in Java is definitely the thinking person's choice in a Java book. Robert S. Stephenson Thanks for writing a great book. The more I read it the better I like it. My students like it, too. Chuck Iverson I just want to commend you for your work on Thinking in Java. It is people like you that dignify the future of the Internet and I just want to thank you for your effort. It is very much appreciated. Patrick Barrell, Network Officer Mamco-QAF Mfg. Most of the Java books out there are fine for a start, and most just have beginning stuff and a lot of the same examples. Yours is by far the best advanced thinking book I've seen. Please publish it soon!... I also bought Thinking in C++ just because I was so impressed with Thinking in Java. George Laframboise, LightWorx Technology Consulting, Inc. I wrote to you earlier about my favorable impressions regarding your Thinking in C++ (a book that stands prominently on my shelf here at work). And today I've been able to delve into Java with your e-book in my virtual hand, and I must say (in my best Chevy Chase from Modern Problems) I like it! Very informative and explanatory, without reading like a dry textbook. You cover the most important yet the least covered concepts of Java development: the whys. Sean Brady Your examples are clear and easy to understand. You took care of many important details of Java that can't be found easily in the weak Java documentation. And you don't waste the reader's time with the basic facts a programmer already knows. Kai Engert, Innovative Software, Germany I'm a great fan of your Thinking in C++ and have recommended it to associates. As I go through the electronic version of your Java book, I'm finding that you've retained the same high level of writing. Thank you! Peter R. Neuwald VERY well-written Java book... I think you've done a GREAT job on it. As the leader of a Chicago-area Java special interest group, I've favorably mentioned your book and website several times at our recent meetings. I would like to use Thinking in Java as the basis for a part of each monthly SIG meeting, in which we review and discuss each chapter in succession. Mark Ertes I really appreciate your work and your book is good. I recommend it here to our users and Ph.D. students. I have Thinking in C++ on order and can't wait to crack it - I'm fairly new to programming and am hitting learning curves head-on everywhere. So this is just a quick note to say thanks for your excellent work. I had begun to burn a little low on enthusiasm from slogging through the mucky, murky prose of most computer books - even ones that came with glowing recommendations. I feel a whole lot better now. Glenn Becker, Educational Theatre Association Thank you for making your wonderful book available. I have found it immensely useful in finally understanding what I experienced as confusing in Java and C++. Reading your book has been very satisfying. Felix Bizaoui, Twin Oaks Industries, Louisa, Va. I must congratulate you on an excellent book. I decided to have a look at Thinking in Java based on my experience with Thinking in C++, and I was not disappointed. Jaco van der Merwe, Software Specialist, DataFusion Systems Ltd, Stellenbosch, South Africa This has to be one of the best Java books I've seen. E.F. Pritchard, Senior Software Engineer, Cambridge Animation Systems Ltd., United Kingdom Your book makes all the other Java books I've read or flipped through seem doubly useless and insulting. Brett g Porter, Senior Programmer, Art and Logic I have been reading your book for a week or two and compared to the books I have read earlier on Java, your book seems to have given me a great start. I have recommended this book to lot of my friends and they have rated it excellent. Please accept my congratulations for coming out with an excellent book. Rama Krishna Bhupathi, Software Engineer, TCSI Corporation, San Jose Just wanted to say what a brilliant piece of work your book is. I've been using it as a major reference for in-house Java work. I find that the table of contents is just right for quickly locating the section that is required. It's also nice to see a book that is not just a rehash of the API nor treats the programmer like a dummy. Grant Sayer, Java Components Group Leader, Ceedata Systems Pty Ltd, Australia Wow! A readable, in-depth Java book. There are a lot of poor (and admittedly a couple of good) Java books out there, but from what I've seen yours is definitely one of the best. John Root, Web Developer, Department of Social Security, London I've *just* started Thinking in Java. I expect it to be very good because I really liked Thinking in C++ (which I read as an experienced C++ programmer, trying to stay ahead of the curve). I'm somewhat less experienced in Java, but expect to be very satisfied. You are a wonderful author. Kevin K. Lewis, Technologist, ObjectSpace, Inc. I think it's a great book. I learned all I know about Java from this book. Thank you for making it available for free over the Internet. If you wouldn't have I'd know nothing about Java at all. But the best thing is that your book isn't a commercial brochure for Java. It also shows the bad sides of Java. YOU have done a great job here. Frederik Fix, Belgium I have been hooked to your books all the time. A couple of years ago, when I wanted to start with C++, it was C++ Inside and Out which took me around the fascinating world of C++. It helped me in getting better opportunities in life. Now, in pursuit of more knowledge and when I wanted to learn Java, I bumped into Thinking in Java - No doubts in my mind as to whether I need some other book. Just fantastic. It is more like rediscovering myself as I get along with the book. It is just a month since I started with Java, and heartfelt thanks to you, I am understanding it better now. Anand Kumar S. Peter Robinson, University of Cambridge Computer Laboratory It's by far the best material I have come across to help me learn Java and I just want you to know how lucky I feel to have found it. THANKS! Chuck Peterson, Product Leader, Internet Product Line, IVIS International The book is great. It's the third book on Java I've started and I'm about two-thirds of the way through it now. I plan to finish this one. I found out about it because it is used in some internal classes at Lucent Technologies and a friend told me the book was on the Net. Good work. Jerry Nowlin, MTS, Lucent Technologies Of the six or so Java books I've accumulated to date, your Thinking in Java is by far the best and clearest. Michael Van Waas, Ph.D., President, TMR Associates I just want to say thanks for Thinking in Java. What a wonderful book you've made here! Not to mention downloadable for free! I have quite a lot of friends here who love programming just as I do, and I've told them about your books. They think it's great! Thanks again! By the way, I'm Indonesian and I live in Java. Ray Frederick Djajadinata, Student at Trisakti University, Jakarta The mere fact that you have made this work free over the Net puts me into shock. I thought I'd let you know how much I appreciate and respect what you're doing. Shane LeBouthillier, Computer Engineering student, University of Alberta, Canada I have to tell you how much I look forward to reading your monthly column. As a newbie to the world of object oriented programming, I appreciate the time and thoughtfulness that you give to even the most elementary topic. I have downloaded your book, but you can bet that I will purchase the hard copy when it is published. Thanks for all of your help. Dan Cashmer, B. C. Ziegler and Co. Just want to congratulate you on a job well done. First I stumbled upon the PDF version of Thinking in Java. However, these books cause my girlfriend to call me a geek. Not that I have anything against the concept - it is just that I thought this phase was well beyond me. But I find myself truly enjoying both books, like no other computer book I have touched or bought so far. Excellent writing style, very nice introduction of every new topic, and lots of wisdom in the books. Well done. Simon Goland, simonsez@smartt.com, Simon Says Consulting, Inc. I must say that your Thinking in Java is great! That is exactly the kind of documentation I was looking for. Especially the sections about good and poor software design using Java 1.1. Dirk Duehr, Lexikon Verlag, Bertelsmann AG, Germany Thank you for writing two great books (Thinking in C++, Thinking in Java). You have helped me immensely in my progression to object oriented programming. Donald Lawson, DCL Enterprises Thank you for taking the time to write a really helpful book on Java. If teaching makes you understand something, by now you must be pretty pleased with yourself. Dominic Turner, GEAC Support It's the best Java book I have ever read - and I read some. Jean-Yves MENGANT, Chief Software Architect NAT-SYSTEM, Paris, France Thinking in Java gives the best coverage and explanation. Very easy to read, and I mean the code fragments as well. Ron Chan, Ph.D., Expert Choice, Inc., Pittsburgh PA Your book is great. I have read lots of programming books and your book still adds insights to programming in my mind. Ningjian Wang, Information System Engineer, The Vanguard Group Thinking in Java is an excellent and readable book. I recommend it to all my students. Jose Suriol, Scylax Corporation Thanks for the opportunity of watching this book grow into a masterpiece! IT IS THE BEST book on the subject that I've read or browsed. Jeff Lapchinsky, Programmer, Net Results Technologies Your book is concise, accessible and a joy to read. Keith Ritchie, Java Research and Development Team, KL Group Inc. It truly is the best book I've read on Java! Daniel Eng The best book I have seen on Java! Rich Hoffarth, Senior Architect, West Group Thank you for a wonderful book. I'm having a lot of fun going through the chapters. Fred Trimble, Actium Corporation You have mastered the art of slowly and successfully making us grasp the details. You make learning VERY easy and satisfying. Thank you for a truly wonderful tutorial. Rajesh Rau, Software Consultant Thinking in Java rocks the free world! Miko O'Sullivan, President, Idocs Inc. About Thinking in C++: Best Book! Winner of the 1995 Software Development Magazine Jolt Award! This book is a tremendous achievement. You owe it to yourself to have a copy on your shelf. That the book is also an excellent tutorial on the ins and outs of C++ is an added bonus. Andrew Binstock Editor, Unix Review Bruce continues to amaze me with his insight into C++, and Thinking in C++ is his best collection of ideas yet. The entire effort is woven in a fabric that includes Eckel's own philosophy of object and program design. Includes index. ISBN 0-13-659723-8 1. Java (Computer program language) I. Title. Heydt Marketing Manager: Miles Williams Cover Design Director: Jerry Votta Cover Design: Daniel Will-Harris Interior Design: Daniel Will-Harris, www.will-harris.com (c) 1998 by Prentice Hall PTR Prentice-Hall Inc. A Simon and Schuster Company Upper Saddle River, NJ 07458 The information in this book is distributed on an as is basis, without warranty. All rights reserved. No part of this book may be reproduced, in any form or by any means, without permission in writing from the publisher. Prentice Hall books are widely used by corporations and government agencies for training, marketing, and resale. The publisher offers discounts on this book when ordered in bulk quantities. For more information, contact the Corporate Sales Department at 800-382-3419, fax: 201-236-7141, email: corpsales@prenhall.com or write: Corporate Sales Department, Prentice Hall PTR, One Lake Street, Upper Saddle River, New Jersey 07458. Java is a registered trademark of Sun Microsystems, Inc. Windows 95 and Windows NT are trademarks of Microsoft Corporation. All other product names and company names mentioned herein are the property of their respective owners. Printed in the United States of America 10 9 8 7 6 5 4 3 2 1 ISBN 0-13-659723-8 Prentice-Hall International (UK) Limited, London Prentice-Hall of Australia Pty. Limited, Sydney Prentice-Hall Canada Inc., Toronto Prentice-Hall Hispanoamericana, S.A., Mexico Prentice-Hall of India Private Limited, New Delhi Prentice-Hall of Japan, Inc., Tokyo Simon and Schuster Asia Pte. 44 Exception handling: dealing with errors 46 Multithreading 47 Persistence 48 Java and the Internet 48 What is the Web? 48 Client-side programming 51 Server-side programming 57 A separate arena: applications 58 Analysis and Design 58 Staying on course 59 Phase 0: Let's make a plan 59 Phase 1: What are we making? 60 Phase 2: How will we build it? 61 Phase 3: Let's build it! 62 Phase 4: Iteration 63 Plans pay off 64 Java vs. 237 The final keyword 238 Final data 238 Final methods 243 Final classes 243 Final caution 244 Initialization and class loading 245 Initialization with inheritance 246 Summary 248 Exercises 248 7: Polymorphism 251 Upcasting 252 Why upcast? 253 The twist 255 Method call binding 255 Producing the right behavior 256 Extensibility 259 Overriding vs. We'll have microbes designed to make food, fuel and plastic; they'll clean up pollution and in general allow us to master the manipulation of the physical world for a fraction of what it costs now. I claimed that it would make the computer revolution look small in comparison. Then I realized I was making a mistake common to science fiction writers: getting lost in the technology (which is of course easy to do in science fiction). An experienced writer knows that the story is never about the things; it's about the people. Genetics will have a very large impact on our lives, but I'm not so sure it will dwarf the computer revolution - or at least the information revolution. Information is about talking to each other: yes, cars and shoes and especially genetic cures are important, but in the end those are just trappings. What truly matters is how we relate to the world. And so much of that is about communication. This book is a case in point. A majority of folks thought I was very bold or a little crazy to put the entire thing up on the Web. Why would anyone buy it? they asked. If I had been of a more conservative nature I wouldn't have done it, but I really didn't want to write another computer book in the same old way. I didn't know what would happen but it turned out to be the smartest thing I've ever done with a book. For one thing, people started sending in corrections. This has been an amazing process, because folks have looked into every nook and cranny and caught both technical and grammatical errors, and I've been able to eliminate bugs of all sorts that I know would have otherwise slipped through. People have been simply terrific about this, very often saying Now, I don't mean this in a critical way and then giving me a collection of errors I'm sure I never would have found. I feel like this has been a kind of group process and it has really made the book into something special. Most people don't want to read the entire book on screen, and hauling around a sheaf of papers, no matter how nicely printed, didn't appeal to them either (plus I think it's not so cheap in terms of laser printer toner). It seems that the computer revolution won't put publishers out of business, after all. However, one student suggested this may become a model for future publishing: books will be published on the Web first, and only if sufficient interest warrants it will the book be put on paper. Currently, the great majority of books of all kinds are financial failures, and perhaps this new approach could make the publishing industry more profitable. This book became an enlightening experience for me in another way. I originally approached Java as just another programming language, which in many senses it is. But as time passed and I studied it more deeply, I began to see that the fundamental intention of the language is different than in all the other languages I have seen. Programming is about managing complexity: the complexity of the problem you want to solve laid upon the complexity of the machine in which it is solved. Because of this complexity, most of our programming projects fail. And yet of all the programming languages that I am aware, none of them have gone all out and decided that their main design goal would be to conquer the complexity of developing and maintaining programs. Of course, many language design decisions were made with complexity in mind, but at some point there were always some other issues that were considered essential to be added into the mix. Inevitably, those other issues are what causes programmers to eventually hit the wall with that language. For example, C++ had to be backwards-compatible with C (to allow easy migration for C programmers), as well as efficient. As another example, Visual Basic (VB) was tied to BASIC, which wasn't really designed to be an extensible language, so all the extensions piled upon VB have produced some truly horrible and un-maintainable syntax. On the other hand, C++, VB and other languages like Smalltalk had some of their design efforts focused on the issue of complexity and as a result are remarkably successful in solving certain types of problems. What has impressed me most as I have come to understand Java is what seems like an unflinching goal of reducing complexity for the programmer. This result alone can save incredible amounts of time and money, but Java doesn't stop there. It goes on to wrap all the complex tasks that have become important, such as multithreading and network programming, in language features or libraries that can at times make those tasks trivial. One of the places I see the greatest impact for this is on the Web. Network programming has always been hard, and Java makes it easy (and they're working on making it easier all the time). Network programming is how we talk to each other more effectively and cheaply than we ever have with telephones (email alone has revolutionized many businesses). As we talk to each other more, amazing things begin to happen, possibly more amazing even than the promise of genetic engineering. And I think that perhaps the results of the communication revolution will not be seen from the effects of moving large quantities of bits around. We shall see the true revolution because we will all be able to talk to each other more easily - one-on-one, but also in groups and as a planet. I've heard it suggested that the next revolution is the formation of a kind of global mind which results from enough people and enough interconnectedness. Java may or may not be the tool that foments that revolution, but at least the possibility has made me feel like I'm doing something meaningful here by attempting to teach the language. If successful, this medium of expression will be significantly easier and more flexible than the alternatives as problems grow larger and more complex. You can't look at Java as just a collection of features; some of the features make no sense in isolation. You can use the sum of the parts only if you are thinking about design, not simply coding. And to understand Java in this way, you must understand the problems with it and with programming in general. This book discusses programming problems, why they are problems, and the approach Java has taken to solve them. Thus, the set of features I explain in each chapter are based on the way I see a particular type of problem being solved with the language. In this way I hope to move you, a little at a time, to the point where the Java mindset becomes your native tongue. Throughout, I'll be taking the attitude that you want to build a model in your head that allows you to develop a deep understanding of the language; if you encounter a puzzle you'll be able to feed it to your model and deduce the answer. However, you might have learned this in many places, such as programming with a macro language or working with a tool like Perl. As long as you've programmed to the point where you feel comfortable with the basic ideas of programming, you'll be able to work through this book. Of course, the book will be easier for the C programmers and more so for the C++ programmers, but don't count yourself out if you're not experienced with those languages (but come willing to work hard). I'll be introducing the concepts of object-oriented programming and Java's basic control mechanisms, so you'll be exposed to those, and the first exercises will involve the basic control-flow statements. Teaching programming languages has become my profession; I've seen nodding heads, blank faces, and puzzled expressions in audiences all over the world since 1989. As I began giving in-house training with smaller groups of people, I discovered something during the exercises. Even those people who were smiling and nodding were confused about many issues. I found out, by chairing the C++ track at the Software Development Conference for the past few years (and now also the Java track), that I and other speakers tended to give the typical audience too many topics too fast. So eventually, through both variety in the audience level and the way that I presented the material, I would end up losing some portion of the audience. Maybe it's asking too much, but because I am one of those people resistant to traditional lecturing (and for most people, I believe, such resistance results from boredom), I wanted to try to keep everyone up to speed. For a time, I was creating a number of different presentations in fairly short order. Thus, I ended up learning by experiment and iteration (a technique that also works well in Java program design). Eventually I developed a course using everything I had learned from my teaching experience - one that I would be happy giving for a long time. It tackles the learning problem in discrete, easy-to-digest steps and in a hands-on seminar (the ideal learning situation), there are exercises following each of the short lessons. Information is available at the same Web site.) The feedback that I get from each seminar helps me change and refocus the material until I think it works well as a teaching medium. But this book isn't just a seminar handout - I tried to pack as much information as I could within these pages and structured it to draw you through onto the next subject. More than anything, the book is designed to serve the solitary reader who is struggling with a new programming language. Goals Like my previous book Thinking in C++, this book has come to be structured around the process of teaching the language. In particular, my motivation is to create something that provides me with a way to teach the language in my own seminars. When I think of a chapter in the book, I think in terms of what makes a good lesson during a seminar. My goal is to get bite-sized pieces that can be taught in a reasonable amount of time, followed by exercises that are feasible to accomplish in a classroom situation. My goals in this book are to: 1. Present the material one simple step at a time so that you can easily digest each concept before moving on. 2. Use examples that are as simple and short as possible. This sometimes prevents me from tackling real world problems, but I've found that beginners are usually happier when they can understand every detail of an example rather than being impressed by the scope of the problem it solves. Also, there's a severe limit to the amount of code that can be absorbed in a classroom situation. For this I will no doubt receive criticism for using toy examples, but I'm willing to accept that in favor of producing something pedagogically useful. 3. Carefully sequence the presentation of features so that you aren't seeing something that you haven't been exposed to. Of course, this isn't always possible; in those situations, a brief introductory description is given. 4. Give you what I think is important for you to understand about the language, rather than everything I know. I believe there is an information importance hierarchy, and that there are some facts that 95 percent of programmers will never need to know and just confuses people and adds to their perception of the complexity of the language. To take an example from C, if you memorize the operator precedence table (I never did), you can write clever code. So forget about precedence, and use parentheses when things aren't clear. 5. Keep each section focused enough so that the lecture time - and the time between exercise periods - is small. Not only does this keep the audience's minds more active and involved during a hands-on seminar, but it gives the reader a greater sense of accomplishment. 6. Provide you with a solid foundation so that you can understand the issues well enough to move on to more difficult coursework and books. Almost all the books published on Java have duplicated this documentation. Chapters This book was designed with one thing in mind: the way people learn the Java language. Seminar audience feedback helped me understand which parts were difficult and needed illumination. As a result, I've taken a great deal of trouble to introduce the features as few at a time as possible. The goal, then, is for each chapter to teach a single feature, or a small group of associated features, in such a way that no additional features are relied upon. That way you can digest each piece in the context of your current knowledge before moving on. Here is a brief description of the chapters contained in the book, which correspond to lectures and exercise periods in my hands-on seminars. Chapter 1: Introduction to objects This chapter is an overview of what object-oriented programming is all about, including the answer to the basic question What's an object?, interface vs. You'll also be introduced to issues of object creation such as constructors, where the objects live, where to put them once they're created, and the magical garbage collector that cleans up the objects that are no longer needed. Other issues will be introduced, including error handling with exceptions, multithreading for responsive user interfaces, and networking and the Internet. You'll also learn about what makes Java special, why it's been so successful, and about object-oriented analysis and design. Chapter 3: Controlling program flow This chapter begins with all of the operators that come to Java from C and C++. In addition, you'll discover common operator pitfalls, casting, promotion, and precedence. Although much of this material has common threads with C and C++ code, there are some differences. In addition, all the examples will be full Java examples so you'll get more comfortable with what Java looks like. Chapter 4: Initialization and cleanup This chapter begins by introducing the constructor, which guarantees proper initialization. The definition of the constructor leads into the concept of function overloading (since you might want several constructors). This is followed by a discussion of the process of cleanup, which is not always as simple as it seems. Normally, you just drop an object when you're done with it and the garbage collector eventually comes along and releases the memory. This portion explores the garbage collector and some of its idiosyncrasies. The chapter concludes with a closer look at how things are initialized: automatic member initialization, specifying member initialization, the order of initialization, static initialization and array initialization. Chapter 5: Hiding the implementation This chapter covers the way that code is packaged together, and why some parts of a library are exposed while other parts are hidden. It begins by looking at the package and import keywords, which perform file-level packaging and allow you to build libraries of classes. The subject of directory paths and file names is also examined. The remainder of the chapter looks at the public, private, and protected keywords, the concept of friendly access, and what the different levels of access control mean when used in various contexts. Chapter 6: Reusing classes The concept of inheritance is standard in virtually all OOP languages. It's a way to take an existing class and add to its functionality (as well as change it, the subject of Chapter 7). Inheritance is often a way to reuse code by leaving the base class the same, and just patching things here and there to produce what you want. However, inheritance isn't the only way to make new classes from existing ones. You can also embed an object inside your new class with composition. In this chapter you'll learn about these two ways to reuse code in Java, and how to apply them. Chapter 7: Polymorphism On your own, you might take nine months to discover and understand polymorphism, a cornerstone of OOP. Through small, simple examples you'll see how to create a family of types with inheritance and manipulate objects in that family through their common base class. Java's polymorphism allows you to treat all objects in this family generically, which means the bulk of your code doesn't rely on specific type information. This makes your programs extensible, so building programs and code maintenance is easier and cheaper. In addition, Java provides a third way to set up a reuse relationship through the interface, which is a pure abstraction of the interface of an object. Once you've seen polymorphism, the interface can be clearly understood. This chapter also introduces Java 1.1 inner classes. Chapter 8: Holding your objects It's a fairly simple program that has only a fixed quantity of objects with known lifetimes. In general, your programs will always be creating new objects at a variety of times that will be known only while the program is running. In addition, you won't know until run-time the quantity or even the exact type of the objects you need. To solve the general programming problem, you need to create any number of objects, anytime, anywhere. This chapter explores in depth the tools that Java supplies to hold objects while you're working with them: the simple arrays and more sophisticated collections (data structures) such as Vector and Hashtable. Finally, the new and improved Java 1.2 collections library is explored in depth. Chapter 9: Error handling with exceptions The basic philosophy of Java is that badly-formed code will not be run. Java has exception handling to deal with any problems that arise while the program is running. This chapter examines how the keywords try, catch, throw, throws, and finally work in Java; when you should throw exceptions and what to do when you catch them. In addition, you'll see Java's standard exceptions, how to create your own, what happens with exceptions in constructors, and how exception handlers are located. Chapter 10: The Java IO system Theoretically, you can divide any program into three parts: input, process, and output. In this chapter you'll learn about the different classes that Java provides for reading and writing files, blocks of memory, and the console. The distinction between old IO and new Java 1.1 IO will be shown. Chapter 11: Run-time type identification Java run-time type identification (RTTI) lets you find the exact type of an object when you have a handle to only the base type. Normally, you'll want to intentionally ignore the exact type of an object and let Java's dynamic binding mechanism (polymorphism) implement the correct behavior for that type. But occasionally it is very helpful to know the exact type of an object for which you have only a base handle. Often this information allows you to perform a special-case operation more efficiently. This chapter explains what RTTI is for, how to use it and how to get rid of it when it doesn't belong there. In addition, the Java 1.1 reflection feature is introduced. Chapter 12: Passing and returning objects Since the only way you talk to objects in Java is through handles, the concepts of passing an object into a function and returning an object from a function have some interesting consequences. This chapter explains what you need to know to manage objects when you're moving in and out of functions, and also shows the String class, which uses a different approach to the problem. Chapter 13: Creating windows and applets Java comes with the Abstract Window Toolkit (AWT), which is a set of classes that handle windowing in a portable fashion; these windowing programs can either be applets or stand-alone applications. This chapter is an introduction to the AWT and the creation of World Wide Web applets. We'll also look at pros and cons of the AWT and the GUI improvements introduced in Java 1.1. The important Java Beans technology is introduced. This is fundamental for the creation of Rapid-Application Development (RAD) program-building tools. Finally, the new Java 1.2 Swing library is introduced - this provides a dramatic improvement in UI components for Java. Chapter 14: Multiple threads Java provides a built-in facility to support multiple concurrent subtasks, called threads, running within a single program. This chapter looks at the syntax and semantics of multithreading in Java. Chapter 15: Network programming All the Java features and libraries seem to really come together when you start writing programs to work across networks. This chapter explores communication across the Internet, and the classes that Java provides to make this easier. It also shows you how to create a Java applet that talks to a common gateway interface (CGI) program, shows you how to write CGI programs in C++ and covers Java 1.1's Java DataBase Connectivity (JDBC) and Remote Method Invocation (RMI). Chapter 16: Design patterns This chapter introduces the very important and yet non-traditional patterns approach to program design. An example of the design evolution process is studied, starting with an initial solution and moving through the logic and process of evolving the solution to more appropriate designs. You'll see one way that a design can materialize over time. Chapter 17: Projects This chapter includes a set of projects that build on the material presented in this book, or otherwise didn't fit in earlier chapters. These projects are significantly more complex than the examples in the rest of the book, and they often demonstrate new techniques and uses of class libraries. There are subjects that didn't seem to fit within the core of the book, and yet I find that I discuss them during seminars. These are placed in the appendices. Appendix A: Using non-Java code A totally portable Java program has serious drawbacks: speed and the inability to access platform-specific services. There are other ways that Java supports non-Java code, including CORBA. This appendix gives you enough of an introduction to these features that you should be able to create simple examples that interface with non-Java code. Appendix B: Comparing C++ and Java If you're a C++ programmer, you already have the basic idea of object-oriented programming, and the syntax of Java no doubt looks very familiar to you. This makes sense because Java was derived from C++. However, there are a surprising number of differences between C++ and Java. These differences are intended to be significant improvements, and if you understand the differences you'll see why Java is such a beneficial programming language. Appendix D: Performance This will allow you to find bottlenecks and improve speed in your Java program. Appendix E: A bit about garbage collection This appendix describes the operation and approaches that are used to implement garbage collection. Appendix F: Recommended reading A list of some of the Java books I've found particularly useful. Exercises I've discovered that simple exercises are exceptionally useful during a seminar to complete a student's understanding, so you'll find a set at the end of each chapter. Most exercises are designed to be easy enough that they can be finished in a reasonable amount of time in a classroom situation while the instructor observes, making sure that all the students are absorbing the material. Some exercises are more advanced to prevent boredom for experienced students. The majority are designed to be solved in a short time and test and polish your knowledge. Some are more challenging, but none present major challenges. Multimedia CD ROM To accompany this book a Multimedia CD ROM is available separately, but this is not like the CDs that you'll usually find packaged with books. Those often only contain the source code for the book. This is more than 15 hours of lectures given by Bruce Eckel, synchronized with 500 slides of information. The seminar is based on this book so it is an ideal accompaniment. The CD ROM contains two versions of this book: 1. A printable version identical to the one available for download. 2. For easy on-screen viewing and reference, a screen-formatted and hyperlinked version which is available exclusively on the CD-ROM. These hyperlinks include: * 230 chapter, section, and sub-heading links * 3600 index links The CD ROM contains over 600MB of content. We believe that it sets a new standard for value. The CD ROM contains everything in the printable version of the book and everything (with the important exception of personalized attention!) from the five-day full-immersion training seminars. We believe that it sets a new standard for quality. The CD ROM is available only by ordering directly from the Web site www.BruceEckel.com. To make sure that you get the most current version, this is the official site for distribution of the code and the electronic version of the book. You may distribute the code in classroom and other educational situations. The primary goal of the copyright is to ensure that the source of the code is properly cited, and to prevent you from republishing the code in print media without permission. Coding standards In the text of this book, identifiers (function, variable and class names) will be set in bold. Most keywords will also be set in bold, except for those keywords that are used so much that the bolding can become tedious, such as class. I use a particular coding style for the examples in this book. This style seems to be supported by most Java development environments. Because Java is a free-form programming language, you can continue to use whatever style you're comfortable with. The programs in this book are files that are included by the word processor in the text, directly from compiled files. Thus, the code files printed in the book should all work without compiler errors. Java versions Although I test the code in this book with several different vendor implementations of Java, I generally rely on the Sun implementation as a reference when determining whether behavior is correct. By the time you read this, Sun will have released three major versions of Java: 1.0, 1.1 and 1.2 (Sun says it will make a major release about every nine months!). One thing you'll notice is that I don't use the sub-revision numbers. At this writing, the released version of 1.0 from Sun was 1.02 and the released version of 1.1 was 1.1.5 (Java 1.2 was in beta). In this book I will refer to Java 1.0, Java 1.1 and Java 1.2 only, to guard against typographical errors produced by further sub-revisioning of these products. Seminars and mentoring My company provides five-day, hands-on, public and in-house training seminars based on the material in this book. Selected material from each chapter represents a lesson, which is followed by a monitored exercise period so each student receives personal attention. The lectures and slides for the introductory seminar are also captured on CD-ROM to provide at least some of the experience of the seminar without the travel and expense. Errors No matter how many tricks a writer uses to detect errors, some always creep in and these often leap off the page for a fresh reader. When you submit a correction, please use the following format: 1. Put TIJ Correction (and nothing else) as the subject line - this way my email program can route it to the right directory. 2. In the body of your email, please use the form: find: one-line string to search for comment: multi-line comment, best starting with here's how I think it should read ### Where the '###' is to indicate the end of comment. This way, my correction tools can do a find using the original text, and your suggested correction will pop up in a window next to it. Suggestions for additional exercises or requests to cover specific topics in the next edition are welcome. Your help is appreciated. Note on the cover design The cover of Thinking in Java is inspired by the American Arts and Crafts Movement, which began near the turn of the century and reached its zenith between 1900 and 1920. It began in England as a reaction to both the machine production of the Industrial Revolution and the highly ornamental style of the Victorian era. Arts and Crafts emphasized spare design, the forms of nature as seen in the art nouveau movement, hand-crafting, and the importance of the individual craftsperson, and yet it did not eschew the use of modern tools. The other theme in this cover suggests a collection box that a naturalist might use to display the insect specimens that he or she has preserved. These insects are objects, placed within the box objects which are themselves placed within the cover object, which illustrates the fundamental concept of aggregation in object-oriented programming. Acknowledgements First of all, thanks to the Doyle Street Cohousing Community for putting up with me for the two years that it took me to write this book (and for putting up with me at all). Thanks very much to Kevin and Sonda Donovan for subletting their great place in gorgeous Crested Butte, Colorado for the summer while I worked on the book. Also thanks to the friendly residents of Crested Butte and the Rocky Mountain Biological Laboratory who made me feel so welcome. The World Gym in Emeryville and its enthusiastic staff helped keep me sane during the final months of the book. This is my first experience using an agent, and I'm not looking back. Thanks to Claudette Moore at Moore Literary Agency for her tremendous patience and perseverance in getting me exactly what I wanted. Jeff appeared at the right place and the right time at Prentice-Hall and has cleared the path and made all the right things happen to make this the most pleasant publishing experience I've ever had. Thanks, Jeff - it means a lot to me. I'm especially indebted to Gen Kiyooka and his company Digigami, who have graciously provided my Web server, and to Scott Callaway who has maintained it. This has been an invaluable aid while I was learning about the Web. Thanks to Cay Horstmann (co-author of Core Java, Prentice Hall 1997), D'Arcy Smith (Symantec), and Paul Tyma (co-author of Java Primer Plus, The Waite Group 1996), for helping me clarify concepts in the language. Thanks to people who have spoken in my Java track at the Software Development Conference, and students in my seminars, who ask the questions I need to hear in order to make the material more clear. Special thanks to Larry and Tina O'Brien, who turned this book and my seminar into a teaching CD ROM. Robert Stephenson, Franklin Chen, Zev Griner, David Karr, Leander A. Stroschein, Steve Clark, Charles A. Lee, Austin Maher, Dennis P. Roth, Roque Oliveira, Douglas Dunn, Dejan Ristic, Neil Galarneau, David B. Malkovsky, Steve Wilkinson, and a host of others. Prof. Marc Meurrens put in a great deal of effort to publicize and make the book available in Europe. They are Kraig Brockschmidt, Gen Kiyooka and Andrea Provaglio, who helps in the understanding of Java and programming in general in Italy. It's not that much of a surprise to me that understanding Delphi helped me understand Java, since there are many concepts and language design decisions in common. My Delphi friends provided assistance by helping me gain insight into that marvelous programming environment. My friend Richard Hale Shaw's insights and support have been very helpful (and Kim's, too). Richard and I spent many months giving seminars together and trying to work out the perfect learning experience for the attendees. Thanks also to KoAnn Vikoren, Eric Faurot, Deborah Sommers, Julie Shaw, Nicole Freeman, Cindy Blair, Barbara Hanscome, Regina Ridley, Alex Dunne, and the rest of the cast and crew at MFI. However, I produced the camera-ready pages myself, so the typesetting errors are mine. Microsoft(r) Word 97 for Windows was used to write the book and to create camera-ready pages. The body typeface is Bitstream Carmina and the headlines are in Bitstream Calligraph 421 (www.bitstream.com). The cover typeface is ITC Rennie Mackintosh. A special thanks to all my teachers and all my students (who are my teachers as well). The most fun writing teacher was Gabrielle Rico (author of Writing the Natural Way, Putnam 1983). I'll always treasure the terrific week at Esalen. And of course, Mom and Dad. Object-oriented programming appeals at multiple levels. For managers, it promises faster and cheaper development and maintenance. For analysts and designers, the modeling process becomes simpler and produces a clear, manageable design. For programmers, the elegance and clarity of the object model and the power of object-oriented tools and libraries makes programming a much more pleasant task, and programmers experience an increase in productivity. Everybody wins, it would seem. If there's a downside, it is the expense of the learning curve. Thinking in objects is a dramatic departure from thinking procedurally, and the process of designing objects is much more challenging than procedural design, especially if you're trying to create reusable objects. In the past, a novice practitioner of object-oriented programming was faced with a choice between two daunting tasks: 1. Choose a language such as Smalltalk in which you had to learn a large library before becoming productive. 2. Choose C++ with virtually no libraries at all,1 and struggle through the depths of the language in order to write your own libraries of objects. It is, in fact, difficult to design objects well - for that matter, it's hard to design anything well. But the intent is that a relatively few experts design the best objects for others to consume. Successful OOP languages incorporate not just language syntax and a compiler, but an entire development environment including a significant library of well-designed, easy to use objects. Thus, the primary job of most programmers is to use existing objects to solve their application problems. The goal of this chapter is to show you what object-oriented programming is and how simple it can be. This chapter will introduce many of the ideas of Java and object-oriented programming on a conceptual level, but keep in mind that you're not expected to be able to write full-fledged Java programs after reading this chapter. All the detailed descriptions and examples will follow throughout the course of this book. The progress of abstraction All programming languages provide abstractions. It can be argued that the complexity of the problems you can solve is directly related to the kind and quality of abstraction. By kind I mean: what is it that you are abstracting? Assembly language is a small abstraction of the underlying machine. Many so-called imperative languages that followed (such as FORTRAN, BASIC, and C) were abstractions of assembly language. These languages are big improvements over assembly language, but their primary abstraction still requires you to think in terms of the structure of the computer rather than the structure of the problem you are trying to solve. The programmer must establish the association between the machine model (in the solution space) and the model of the problem that is actually being solved (in the problem space). The alternative to modeling the machine is to model the problem you're trying to solve. Early languages such as LISP and APL chose particular views of the world (all problems are ultimately lists or all problems are algorithmic). PROLOG casts all problems into chains of decisions. Languages have been created for constraint-based programming and for programming exclusively by manipulating graphical symbols. The object-oriented approach takes a step farther by providing tools for the programmer to represent elements in the problem space. This representation is general enough that the programmer is not constrained to any particular type of problem. This is a more flexible and powerful language abstraction than what we've had before. Thus OOP allows you to describe the problem in terms of the problem, rather than in the terms of the solution. There's still a connection back to the computer, though. Each object looks quite a bit like a little computer; it has a state, and it has operations you can ask it to perform. However, this doesn't seem like such a bad analogy to objects in the real world; they all have characteristics and behaviors. Alan Kay summarized five basic characteristics of Smalltalk, the first successful object-oriented language and one of the languages upon which Java is based. These characteristics represent a pure approach to object-oriented programming: 1. Everything is an object. Think of an object as a fancy variable; it stores data, but you can also ask it to perform operations on itself by making requests. In theory, you can take any conceptual component in the problem you're trying to solve (dogs, buildings, services, etc.) and represent it as an object in your program. 2. A program is a bunch of objects telling each other what to do by sending messages. To make a request of an object, you send a message to that object. More concretely, you can think of a message as a request to call a function that belongs to a particular object. 3. Each object has its own memory made up of other objects. Or, you make a new kind of object by making a package containing existing objects. Thus, you can build up complexity in a program while hiding it behind the simplicity of objects. 4. Every object has a type. This is actually a very loaded statement, as you will see later. Because an object of type circle is also an object of type shape, a circle is guaranteed to receive shape messages. This means you can write code that talks to shapes and automatically handle anything that fits the description of a shape. This substitutability is one of the most powerful concepts in OOP. Simula, as its name implies, was created for developing simulations such as the classic bank teller problem. In this, you have a bunch of tellers, customers, accounts, transactions, etc. The members (elements) of each class share some commonality: every account has a balance, every teller can accept a deposit, etc. At the same time, each member has its own state; each account has a different balance, each teller has a name. Thus the tellers, customers, accounts, transactions, etc. This entity is the object, and each object belongs to a particular class that defines its characteristics and behaviors. So, although what we really do in object-oriented programming is create new data types, virtually all object-oriented programming languages use the class keyword. When you see the word type think class and vice versa. Once a type is established, you can make as many objects of that type as you like, and then manipulate those objects as the elements that exist in the problem you are trying to solve. But how do you get an object to do useful work for you? There must be a way to make a request of that object so it will do something, such as complete a transaction, draw something on the screen or turn on a switch. And each object can satisfy only certain requests. The requests you can make of an object are defined by its interface, and the type is what determines the interface. The idea of type being equivalent to interface is fundamental in object-oriented programming. You create a handle for a Light simply by declaring a name (lt) for that identifier, and you make an object of type Light with the new keyword, assigning it to the handle with the = sign. To send a message to the object, you state the handle name and connect it to the message name with a period (dot). From the standpoint of the user of a pre-defined class, that's pretty much all there is to programming with objects. The hidden implementation It is helpful to break up the playing field into class creators (those who create new data types) and client programmers4 (the class consumers who use the data types in their applications). The goal of the client programmer is to collect a toolbox full of classes to use for rapid application development. The goal of the class creator is to build a class that exposes only what's necessary to the client programmer and keeps everything else hidden. If it's hidden, the client programmer can't use it, which means that the class creator can change the hidden portion at will without worrying about the impact to anyone else. The interface establishes what requests you can make for a particular object. However, there must be code somewhere to satisfy that request. This, along with the hidden data, comprises the implementation. From a procedural programming standpoint, it's not that complicated. A type has a function associated with each possible request, and when you make a particular request to an object, that function is called. This process is often summarized by saying that you send a message (make a request) to an object, and the object figures out what to do with that message (it executes code). In any relationship it's important to have boundaries that are respected by all parties involved. When you create a library, you establish a relationship with the client programmer, who is another programmer, but one who is putting together an application or using your library to build a bigger library. If all the members of a class are available to everyone, then the client programmer can do anything with that class and there's no way to force any particular behaviors. Even though you might really prefer that the client programmer not directly manipulate some of the members of your class, without access control there's no way to prevent it. Everything's naked to the world. There are two reasons for controlling access to members. The first is to keep client programmers' hands off portions they shouldn't touch - parts that are necessary for the internal machinations of the data type but not part of the interface that users need to solve their particular problems. This is actually a service to users because they can easily see what's important to them and what they can ignore. The second reason for access control is to allow the library designer to change the internal workings of the structure without worrying about how it will affect the client programmer. For example, you might implement a particular class in a simple fashion to ease development, and then later decide you need to rewrite it to make it run faster. If the interface and implementation are clearly separated and protected, you can accomplish this and require only a relink by the user. Java uses three explicit keywords and one implied keyword to set the boundaries in a class: public, private, protected and the implied friendly, which is what you get if you don't specify one of the other keywords. Their use and meaning are remarkably straightforward. These access specifiers determine who can use the definition that follows. The private keyword, on the other hand, means that no one can access that definition except you, the creator of the type, inside function members of that type. If someone tries to access a private member, they'll get a compile-time error. Friendly has to do with something called a package, which is Java's way of making libraries. If something is friendly it's available only within the package. Inheritance will be covered shortly. Reusing the implementation Once a class has been created and tested, it should (ideally) represent a useful unit of code. It turns out that this reusability is not nearly so easy to achieve as many would hope; it takes experience and insight to achieve a good design. But once you have such a design, it begs to be reused. Code reuse is arguably the greatest leverage that object-oriented programming languages provide. The simplest way to reuse a class is to just use an object of that class directly, but you can also place an object of that class inside a new class. We call this creating a member object. Your new class can be made up of any number and type of other objects, whatever is necessary to achieve the functionality desired in your new class. This concept is called composition, since you are composing a new class from existing classes. Sometimes composition is referred to as a has-a relationship, as in a car has a trunk. Composition comes with a great deal of flexibility. The member objects of your new class are usually private, making them inaccessible to client programmers using the class. This allows you to change those members without disturbing existing client code. You can also change the member objects at run time, which provides great flexibility. Inheritance, which is described next, does not have this flexibility since the compiler must place restrictions on classes created with inheritance. Because inheritance is so important in object-oriented programming it is often highly emphasized, and the new programmer can get the idea that inheritance should be used everywhere. This can result in awkward and overcomplicated designs. Instead, you should first look to composition when creating new classes, since it is simpler and more flexible. If you take this approach, your designs will stay cleaner. It will be reasonably obvious when you need inheritance. Inheritance: reusing the interface By itself, the concept of an object is a convenient tool. It allows you to package data and functionality together by concept, so you can represent an appropriate problem-space idea rather than being forced to use the idioms of the underlying machine. These concepts are expressed in the primary idea of the programming language as a data type (using the class keyword). It seems a pity, however, to go to all the trouble to create a data type and then be forced to create a brand new one that might have similar functionality. It's nicer if we can take the existing data type, clone it and make additions and modifications to the clone. Inheritance is implemented in Java with the extends keyword. You make a new class and you say that it extends an existing class. That is, all the messages you can send to objects of the base class you can also send to objects of the derived class. Since we know the type of a class by the messages we can send to it, this means that the derived class is the same type as the base class. This type equivalence via inheritance is one of the fundamental gateways in understanding the meaning of object-oriented programming. Since both the base class and derived class have the same interface, there must be some implementation to go along with that interface. That is, there must be a method to execute when an object receives a particular message. If you simply inherit a class and don't do anything else, the methods from the base-class interface come right along into the derived class. That means objects of the derived class have not only the same type, they also have the same behavior, which doesn't seem particularly interesting. You have two ways to differentiate your new derived class from the original base class it inherits from. The first is quite straightforward: you simply add brand new functions to the derived class. These new functions are not part of the base class interface. This means that the base class simply didn't do as much as you wanted it to, so you add more functions. This simple and primitive use for inheritance is, at times, the perfect solution to your problem. However, you should look closely for the possibility that your base class might need these additional functions. Overriding base-class functionality Although the extends keyword implies that you are going to add new functions to the interface, that's not necessarily true. The second way to differentiate your new class is to change the behavior of an existing base-class function. This is referred to as overriding that function. To override a function, you simply create a new definition for the function in the derived class. You're saying I'm using the same interface function here, but I want it to do something different for my new type. Is-a vs. This means that the derived type is exactly the same type as the base class since it has exactly the same interface. As a result, you can exactly substitute an object of the derived class for an object of the base class. This can be thought of as pure substitution. In a sense, this is the ideal way to treat inheritance. There are times when you must add new interface elements to a derived type, thus extending the interface and creating a new type. The new type can still be substituted for the base type, but the substitution isn't perfect in a sense because your new functions are not accessible from the base type. This can be described as an is-like-a relationship; the new type has the interface of the old type but it also contains other functions, so you can't really say it's exactly the same. For example, consider an air conditioner. Suppose your house is wired with all the controls for cooling; that is, it has an interface that allows you to control cooling. Imagine that the air conditioner breaks down and you replace it with a heat pump, which can both heat and cool. The heat pump is-like-an air conditioner, but it can do more. Because your house is wired only to control cooling, it is restricted to communication with the cooling part of the new object. The interface of the new object has been extended, and the existing system doesn't know about anything except the original interface. When you see the substitution principle it's easy to feel like that's the only way to do things, and in fact it is nice if your design works out that way. But you'll find that there are times when it's equally clear that you must add new functions to the interface of a derived class. With inspection both cases should be reasonably obvious. Interchangeable objects with polymorphism Inheritance usually ends up creating a family of classes, all based on the same uniform interface. We express this with an inverted tree diagram:5 One of the most important things you do with such a family of classes is to treat an object of a derived class as an object of the base class. This is important because it means you can write a single piece of code that ignores the specific details of type and talks just to the base class. That code is then decoupled from type-specific information, and thus is simpler to write and easier to understand. And, if a new type - a Triangle, for example - is added through inheritance, the code you write will work just as well for the new type of Shape as it did on the existing types. Thus the program is extensible. Consider the above example. This is actually a pretty amazing trick. Consider the line: doStuff(c); What's happening here is that a Circle handle is being passed into a function that's expecting a Shape handle. Since a Circle is a Shape it can be treated as one by doStuff( ). That is, any message that doStuff( ) can send to a Shape, a Circle can accept. So it is a completely safe and logical thing to do. We call this process of treating a derived type as though it were its base type upcasting. The name cast is used in the sense of casting into a mold and the up comes from the way the inheritance diagram is typically arranged, with the base type at the top and the derived classes fanning out downward. Thus, casting to a base type is moving up the inheritance diagram: upcasting. An object-oriented program contains some upcasting somewhere, because that's how you decouple yourself from knowing about the exact type you're working with. Here, you just say You're a shape, I know you can erase( ) yourself, do it and take care of the details correctly. Dynamic binding What's amazing about the code in doStuff( ) is that somehow the right thing happens. This is amazing because when the Java compiler is compiling the code for doStuff( ), it cannot know exactly what types it is dealing with. So ordinarily, you'd expect it to end up calling the version of erase( ) for Shape, and draw( ) for Shape and not for the specific Circle, Square, or Line. And yet the right thing happens. Here's how it works. When you send a message to an object even though you don't know what specific type it is, and the right thing happens, that's called polymorphism. The process used by object-oriented programming languages to implement polymorphism is called dynamic binding. The compiler and run-time system handle the details; all you need to know is that it happens and more importantly how to design with it. Some languages require you to use a special keyword to enable dynamic binding. In C++ this keyword is virtual. In Java, you never need to remember to add a keyword because functions are automatically dynamically bound. So you can always expect that when you send a message to an object, the object will do the right thing, even when upcasting is involved. Abstract base classes and interfaces Often in a design, you want the base class to present only an interface for its derived classes. That is, you don't want anyone to actually create an object of the base class, only to upcast to it so that its interface can be used. This is accomplished by making that class abstract using the abstract keyword. If anyone tries to make an object of an abstract class, the compiler prevents them. This is a tool to enforce a particular design. When the class is inherited, that method must be implemented, or the inherited class becomes abstract as well. Creating an abstract method allows you to put a method in an interface without being forced to provide a possibly meaningless body of code for that method. The interface keyword takes the concept of an abstract class one step further by preventing any function definitions at all. The interface is a very useful and commonly-used tool, as it provides the perfect separation of interface and implementation. In addition, you can combine many interfaces together, if you wish. The remainder of this section will cover these issues. One of the most important factors is the way objects are created and destroyed. Where is the data for an object and how is the lifetime of the object controlled? There are different philosophies at work here. C++ takes the approach that control of efficiency is the most important issue, so it gives the programmer a choice. For maximum run-time speed, the storage and lifetime can be determined while the program is being written, by placing the objects on the stack (these are sometimes called automatic or scoped variables) or in the static storage area. This places a priority on the speed of storage allocation and release, and control of these can be very valuable in some situations. However, you sacrifice flexibility because you must know the exact quantity, lifetime and type of objects while you're writing the program. If you are trying to solve a more general problem such as computer-aided design, warehouse management or air-traffic control, this is too restrictive. The second approach is to create objects dynamically in a pool of memory called the heap. In this approach you don't know until run time how many objects you need, what their lifetime is or what their exact type is. Those are determined at the spur of the moment while the program is running. If you need a new object, you simply make it on the heap at the point that you need it. Because the storage is managed dynamically, at run time, the amount of time required to allocate storage on the heap is significantly longer than the time to create storage on the stack. In addition, the greater flexibility is essential to solve the general programming problem. C++ allows you to determine whether the objects are created while you write the program or at run time to allow the control of efficiency. You might think that since it's more flexible, you'd always want to create objects on the heap rather than the stack. There's another issue, however, and that's the lifetime of an object. If you create an object on the stack or in static storage, the compiler determines how long the object lasts and can automatically destroy it. However, if you create it on the heap the compiler has no knowledge of its lifetime. Of course, a garbage collector is much more convenient, but it requires that all applications must be able to tolerate the existence of the garbage collector and the other overhead for garbage collection. This does not meet the design requirements of the C++ language and so it was not included, but Java does have a garbage collector (as does Smalltalk; Delphi does not but one could be added. Third-party garbage collectors exist for C++). The rest of this section looks at additional factors concerning object lifetimes and landscapes. Collections and iterators If you don't know how many objects you're going to need to solve a particular problem, or how long they will last, you also don't know how to store those objects. How can you know how much space to create for those objects? You can't, since that information isn't known until run time. The solution to most problems in object-oriented design seems flippant: you create another type of object. The new type of object that solves this particular problem holds handles to other objects. Of course, you can do the same thing with an array, which is available in most languages. But there's more. So you don't need to know how many objects you're going to hold in a collection. Just create a collection object and let it take care of the details. Fortunately, a good OOP language comes with a set of collections as part of the package. In C++, it's the Standard Template Library (STL). Object Pascal has collections in its Visual Component Library (VCL). Smalltalk has a very complete set of collections. Java also has collections in its standard library. These may include sets, queues, hash tables, trees, stacks, etc. All collections have some way to put things in and get things out. The way that you place something into a collection is fairly obvious. There's a function called push or add or a similar name. Fetching things out of a collection is not always as apparent; if it's an array-like entity such as a vector, you might be able to use an indexing operator or function. But in many situations this doesn't make sense. Also, a single-selection function is restrictive. What if you want to manipulate or compare a set of elements in the collection instead of just one? The solution is an iterator, which is an object whose job is to select the elements within a collection and present them to the user of the iterator. As a class, it also provides a level of abstraction. This abstraction can be used to separate the details of the collection from the code that's accessing that collection. The collection, via the iterator, is abstracted to be simply a sequence. The iterator allows you to traverse that sequence without worrying about the underlying structure - that is, whether it's a vector, a linked list, a stack or something else. This gives you the flexibility to easily change the underlying data structure without disturbing the code in your program. Java began (in version 1.0 and 1.1) with a standard iterator, called Enumeration, for all of its collection classes. Java 1.2 has added a much more complete collection library which contains an iterator called Iterator that does more than the older Enumeration. From the design standpoint, all you really want is a sequence that can be manipulated to solve your problem. If a single type of sequence satisfied all of your needs, there'd be no reason to have different kinds. There are two reasons that you need a choice of collections. First, collections provide different types of interfaces and external behavior. A stack has a different interface and behavior than that of a queue, which is different than that of a set or a list. One of these might provide a more flexible solution to your problem than the other. Second, different collections have different efficiencies for certain operations. The best example is a vector and a list. Both are simple sequences that can have identical interfaces and external behaviors. But certain operations can have radically different costs. Randomly accessing elements in a vector is a constant-time operation; it takes the same amount of time regardless of the element you select. However, in a linked list it is expensive to move through the list to randomly select an element, and it takes longer to find an element if it is further down the list. On the other hand, if you want to insert an element in the middle of a sequence, it's much cheaper in a list than in a vector. These and other operations have different efficiencies depending upon the underlying structure of the sequence. In the design phase, you might start with a list and, when tuning for performance, change to a vector. Because of the abstraction via iterators, you can change from one to the other with minimal impact on your code. In the end, remember that a collection is only a storage cabinet to put objects in. If that cabinet solves all of your needs, it doesn't really matter how it is implemented (a basic concept with most types of objects). You might need only one type of sequence. You can even imagine the perfect collection abstraction, which can automatically change its underlying implementation according to the way it is used. The singly-rooted hierarchy One of the issues in OOP that has become especially prominent since the introduction of C++ is whether all classes should ultimately be inherited from a single base class. In Java (as with virtually all other OOP languages) the answer is yes and the name of this ultimate base class is simply Object. It turns out that the benefits of the singly-rooted hierarchy are many. All objects in a singly-rooted hierarchy have an interface in common, so they are all ultimately the same type. The alternative (provided by C++) is that you don't know that everything is the same fundamental type. And in any new class library you acquire, some other incompatible interface will be used. It requires effort (and possibly multiple inheritance) to work the new interface into your design. Is the extra flexibility of C++ worth it? If you need it - if you have a large investment in C - it's quite valuable. If you're starting from scratch, other alternatives such as Java can often be more productive. All objects in a singly-rooted hierarchy (such as Java provides) can be guaranteed to have certain functionality. You know you can perform certain basic operations on every object in your system. A singly-rooted hierarchy, along with creating all objects on the heap, greatly simplifies argument passing (one of the more complex topics in C++). A singly-rooted hierarchy makes it much easier to implement a garbage collector. The necessary support can be installed in the base class, and the garbage collector can thus send the appropriate messages to every object in the system. Without a singly-rooted hierarchy and a system to manipulate an object via a handle, it is difficult to implement a garbage collector. Since run-time type information is guaranteed to be in all objects, you'll never end up with an object whose type you cannot determine. This is especially important with system level operations, such as exception handling, and to allow greater flexibility in programming. You may wonder why, if it's so beneficial, a singly-rooted hierarchy isn't it in C++. It's the old bugaboo of efficiency and control. A singly-rooted hierarchy puts constraints on your program designs, and in particular it was perceived to put constraints on the use of existing C code. Java provides such a library, although it is fairly limited in Java 1.0 and 1.1 (the Java 1.2 collections library, however, satisfies most needs). Downcasting vs. The singly-rooted hierarchy means that everything is an Object, so a collection that holds Objects can hold anything. This makes it easy to reuse. To use such a collection, you simply add object handles to it, and later ask for them back. But, since the collection holds only Objects, when you add your object handle into the collection it is upcast to Object, thus losing its identity. When you fetch it back, you get an Object handle, and not a handle to the type that you put in. So how do you turn it back into something that has the useful interface of the object that you put into the collection? Here, the cast is used again, but this time you're not casting up the inheritance hierarchy to a more general type, you cast down the hierarchy to a more specific type. This manner of casting is called downcasting. It's not completely dangerous, however, because if you downcast to the wrong thing you'll get a run-time error called an exception, which will be described shortly. When you fetch object handles from a collection, though, you must have some way to remember exactly what they are so you can perform a proper downcast. Downcasting and the run-time checks require extra time for the running program, and extra effort from the programmer. Wouldn't it make sense to somehow create the collection so that it knows the types that it holds, eliminating the need for the downcast and possible mistake? The solution is parameterized types, which are classes that the compiler can automatically customize to work with particular types. For example, with a parameterized collection, the compiler could customize that collection so that it would accept only Shapes and fetch only Shapes. Parameterized types are an important part of C++, partly because C++ has no singly-rooted hierarchy. In C++, the keyword that implements parameterized types is template. Java currently has no parameterized types since it is possible for it to get by - however awkwardly - using the singly-rooted hierarchy. The housekeeping dilemma: who should clean up? Each object requires resources in order to exist, most notably memory. When an object is no longer needed it must be cleaned up so that these resources are released for reuse. In simple programming situations the question of how an object is cleaned up doesn't seem too challenging: you create the object, use it for as long as it's needed, and then it should be destroyed. It's not too hard, however, to encounter situations in which the situation is more complex. Suppose, for example, you are designing a system to manage air traffic for an airport. For cleanup, simply delete the appropriate airplane object when a plane leaves the zone. But perhaps you have some other system to record data about the planes; perhaps data that doesn't require such immediate attention as the main controller function. Maybe it's a record of the flight plans of all the small planes that leave the airport. So you have a second collection of small planes, and whenever you create a plane object you also put it in this collection if it's a small plane. Then some background process performs operations on the objects in this collection during idle moments. Now the problem is more difficult: how can you possibly know when to destroy the objects? When you're done with the object, some other part of the system might not be. The garbage collector knows when an object is no longer in use, and it then automatically releases the memory for that object. Garbage collectors vs. Well of course there's a price you pay for all this programming convenience, and that price is run-time overhead. As mentioned before, in C++ you can create objects on the stack, and in this case they're automatically cleaned up (but you don't have the flexibility of creating as many as you want at run-time). Creating objects on the stack is the most efficient way to allocate storage for objects and to free that storage. Creating objects on the heap can be much more expensive. Always inheriting from a base class and making all function calls polymorphic also exacts a small toll. But the garbage collector is a particular problem because you never quite know when it's going to start up or how long it will take. This means that there's an inconsistency in the rate of execution of a Java program, so you can't use it in certain situations, such as when the rate of execution of a program is uniformly critical. This goal was realized, but at the price of greater complexity when programming in C++. Java is simpler than C++, but the tradeoff is in efficiency and sometimes applicability. For a significant portion of programming problems, however, Java is often the superior choice. Exception handling: dealing with errors Ever since the beginning of programming languages, error handling has been one of the most difficult issues. A major problem with most error-handling schemes is that they rely on programmer vigilance in following an agreed-upon convention that is not enforced by the language. If the programmer is not vigilant, which is often if they are in a hurry, these schemes can easily be forgotten. Exception handling wires error handling directly into the programming language and sometimes even the operating system. An exception is an object that is thrown from the site of the error and can be caught by an appropriate exception handler designed to handle that particular type of error. It's as if exception handling is a different, parallel path of execution that can be taken when things go wrong. And because it uses a separate execution path, it doesn't need to interfere with your normally-executing code. This makes that code simpler to write since you aren't constantly forced to check for errors. In addition, a thrown exception is unlike an error value that's returned from a function or a flag that's set by a function in order to indicate an error condition, These can be ignored. An exception cannot be ignored so it's guaranteed to be dealt with at some point. Finally, exceptions provide a way to reliably recover from a bad situation. Instead of just exiting you are often able to set things right and restore the execution of a program, which produces much more robust programs. Java's exception handling stands out among programming languages, because in Java, exception-handling was wired in from the beginning and you're forced to use it. If you don't write your code to properly handle exceptions, you'll get a compile-time error message. This guaranteed consistency makes error-handling much easier. It's worth noting that exception handling isn't an object-oriented feature, although in object-oriented languages the exception is normally represented with an object. Exception handling existed before object-oriented languages. Multithreading A fundamental concept in computer programming is the idea of handling more than one task at a time. Many programming problems require that the program be able to stop what it's doing, deal with some other problem and return to the main process. The solution has been approached in many ways. Initially, programmers with low-level knowledge of the machine wrote interrupt service routines and the suspension of the main process was initiated through a hardware interrupt. Although this worked well, it was difficult and non-portable, so it made moving a program to a new type of machine slow and expensive. Within a program, these separately-running pieces are called threads and the general concept is called multithreading. A common example of multithreading is the user interface. By using threads, a user can press a button and get a quick response rather than being forced to wait until the program finishes its current task. Ordinarily, threads are just a way to allocate the time of a single processor. But if the operating system supports multiple processors, each thread can be assigned to a different processor and they can truly run in parallel. One of the convenient features of multithreading at the language level is that the programmer doesn't need to worry about whether there are many processors or just one. The program is logically divided into threads and if the machine has more than one processor then the program runs faster, without any special adjustments. All this makes threading sound pretty simple. There is a catch: shared resources. If you have more than one thread running that's expecting to access the same resource you have a problem. For example, two processes can't simultaneously send information to a printer. To solve the problem, resources that can be shared, such as the printer, must be locked while they are being used. So a thread locks a resource, completes its task and then releases the lock so that someone else can use the resource. Java's threading is built into the language, which makes a complicated subject much simpler. The threading is supported on an object level, so one thread of execution is represented by one object. Java also provides limited resource locking. It can lock the memory of any object (which is, after all, one kind of shared resource) so that only one thread can use it at a time. This is accomplished with the synchronized keyword. Other types of resources must be locked explicitly by the programmer, typically by creating an object to represent the lock that all threads must check before accessing that resource. Persistence When you create an object, it exists for as long as you need it, but under no circumstances does it exist when the program terminates. While this makes sense at first, there are situations in which it would be incredibly useful if an object could exist and hold its information even while the program wasn't running. Then the next time you started the program, the object would be there and it would have the same information it had the previous time the program was running. Java 1.1 provides support for lightweight persistence, which means that you can easily store objects on disk and later retrieve them. The reason it's lightweight is that you're still forced to make explicit calls to do the storage and retrieval. In some future release more complete support for persistence might appear. Java and the Internet If Java is, in fact, yet another computer programming language, you may question why it is so important and why it is being promoted as a revolutionary step in computer programming. The answer isn't immediately obvious if you're coming from a traditional programming perspective. Although Java will solve traditional stand-alone programming problems, the reason it is important is that it will also solve programming problems on the World Wide Web. What is the Web? The Web can seem a bit of a mystery at first, with all this talk of surfing, presence and home pages. There has even been a growing reaction against Internet-mania, questioning the economic value and outcome of such a sweeping movement. Taken together, the information repository, the software that distributes the information and the machine(s) where the information and software reside is called the server. The software that resides on the remote machine, and that communicates with the server, fetches the information, processes it, and displays it on the remote machine is called the client. The problems arise because you have a single server trying to serve many clients at once. Generally a database management system is involved so the designer balances the layout of data into tables for optimal use. In addition, systems often allow a client to insert new information into a server. This means you must ensure that one client's new data doesn't walk over another client's new data, or that data isn't lost in the process of adding it to the database. It's especially problematic to support multiple types of computers and operating systems. Finally, there's the all-important performance issue: you might have hundreds of clients making requests of your server at any one time, and so any small delay is crucial. To minimize latency, programmers work hard to offload processing tasks, often to the client machine but sometimes to other machines at the server site using so-called middleware. It's responsible for everything from taking orders and credit-card transactions to the distribution of any kind of data - stock market, scientific, government - you name it. What we've come up with in the past is individual solutions to individual problems, inventing a new solution each time. These were hard to create and hard to use and the user had to learn a new interface for each one. The Web as a giant server The Web is actually one giant client-server system. It's a bit worse than that, since you have all the servers and clients coexisting on a single network at once. You don't need to know that, since all you care about is connecting to and interacting with one server at a time (even though you might be hopping around the world in your search for the correct server). Initially it was a simple one-way process. You made a request of a server and it handed you a file, which your machine's browser software (i.e. the client) would interpret by formatting onto your local machine. But in short order people began wanting to do more than just deliver pages from a server. These are the changes we've been seeing in the development of the Web. The Web browser was a big step forward: the concept that one piece of information could be displayed on any type of computer without change. However, browsers were still rather primitive and rapidly bogged down by the demands placed on them. They weren't particularly interactive and tended to clog up both the server and the Internet because any time you needed to do something that required programming you had to send information back to the server to be processed. It could take many seconds or minutes to find out you had misspelled something in your request. Since the browser was just a viewer it couldn't perform even the simplest computing tasks. To begin with, graphics standards have been enhanced to allow better animation and video within browsers. The remainder of the problem can be solved only by incorporating the ability to run programs on the client end, under the browser. This is called client-side programming. Client-side programming8 The Web's initial server-browser design provided for interactive content, but the interactivity was completely provided by the server. The server produced static pages for the client browser, which would simply interpret and display them. This submission passes through the Common Gateway Interface (CGI) provided on all Web servers. The text within the submission tells CGI what to do with it. Perl is a common choice because it is designed for text manipulation and is interpreted, so it can be installed on any server regardless of processor or operating system. Many powerful Web sites today are built strictly on CGI, and you can in fact do nearly anything with it. The problem is response time. The response of a CGI program depends on how much data must be sent as well as the load on both the server and the Internet. For example, any sort of dynamic graphing is nearly impossible to perform with consistency because a GIF file must be created and moved from the server to the client for each version of the graph. And you've no doubt had direct experience with something as simple as validating the data on an input form. Not only is this slow, it's not elegant. The solution is client-side programming. Most machines that run Web browsers are powerful engines capable of doing vast work, and with the original static HTML approach they are sitting there, just idly waiting for the server to dish up the next page. Client-side programming means that the Web browser is harnessed to do whatever work it can, and the result for the user is a much speedier and more interactive experience at your Web site. The problem with discussions of client-side programming is that they aren't very different from discussions of programming in general. The parameters are almost the same, but the platform is different: a Web browser is like a limited operating system. In the end, it's still programming and this accounts for the dizzying array of problems and solutions produced by client-side programming. The rest of this section provides an overview of the issues and approaches in client-side programming. Plug-ins One of the most significant steps forward in client-side programming is the development of the plug-in. This is a way for a programmer to add new functionality to the browser by downloading a piece of code that plugs itself into the appropriate spot in the browser. The value of the plug-in for client-side programming is that it allows an expert programmer to develop a new language and add that language to a browser without the permission of the browser manufacturer. Thus, plug-ins provide the back door that allows the creation of new client-side programming languages (although not all languages are implemented as plug-ins). Scripting languages Plug-ins resulted in an explosion of scripting languages. With a scripting language you embed the source code for your client-side program directly into the HTML page and the plug-in that interprets that language is automatically activated while the HTML page is being displayed. Scripting languages tend to be reasonably simple to understand, and because they are simply text that is part of an HTML page they load very quickly as part of the single server hit required to procure that page. The trade-off is that your code is exposed for everyone to see (and steal) but generally you aren't doing amazingly sophisticated things with scripting languages so it's not too much of a hardship. This points out that scripting languages are really intended to solve specific types of problems, primarily the creation of richer and more interactive graphical user interfaces (GUIs). However, a scripting language might solve 80 percent of the problems encountered in client-side programming. There are others out there and no doubt more in development. JavaScript is probably the most commonly supported. It comes built into both Netscape Navigator and the Microsoft Internet Explorer (IE). In addition, there are probably more JavaScript books out than for the other languages, and some tools automatically create pages using JavaScript. Java allows client-side programming via the applet. An applet is a mini-program that will run only under a Web browser. The applet is downloaded automatically as part of a Web page (just as, for example, a graphic is automatically downloaded). When the applet is activated it executes a program. This is part of its beauty - it provides you with a way to automatically distribute the client software from the server at the time the user needs the client software, and no sooner. They get the latest version of the client software without fail and without difficult re-installation. Because of the way Java is designed, the programmer needs to create only a single program, and that program automatically works with all computers that have browsers with built-in Java interpreters. Not only do you get the immediate win of speed and responsiveness, but the general network traffic and load upon servers can be reduced, preventing the entire Internet from slowing down. One advantage a Java applet has over a scripted program is that it's in compiled form, so the source code isn't available to the client. On the other hand, a Java applet can be decompiled without too much trouble, and hiding your code is often not an important issue anyway. Two other factors can be important. As you will see later in the book, a compiled Java applet can comprise many modules and take multiple server hits (accesses) to download. This could be important to the responsiveness of your Web site. Another factor is the all-important learning curve. Regardless of what you've heard, Java is not a trivial language to learn. If you're experienced with a scripting language you will certainly benefit from looking at JavaScript or VBScript before committing to Java, since they might fit your needs handily and you'll be more productive sooner. ActiveX To some degree, the competitor to Java is Microsoft's ActiveX, although it takes a completely different approach. ActiveX is originally a Windows-only solution, although it is now being developed via an independent consortium to become cross-platform. If, for example, you're already an experienced Windows programmer using a language such as C++, Visual Basic, or Borland's Delphi, you can create ActiveX components with almost no changes to your programming knowledge. ActiveX also provides a path for the use of legacy code in your Web pages. Security Automatically downloading and running programs across the Internet can sound like a virus-builder's dream. ActiveX especially brings up the thorny issue of security in client-side programming. If you click on a Web site, you might automatically download any number of things along with the HTML page: GIF files, script code, compiled Java code, and ActiveX components. Some of these are benign; GIF files can't do any harm, and scripting languages are generally limited in what they can do. Java was also designed to run its applets within a sandbox of safety, which prevents it from writing to disk or accessing memory outside the sandbox. ActiveX is at the opposite end of the spectrum. Programming with ActiveX is like programming Windows - you can do anything you want. So if you click on a page that downloads an ActiveX component, that component might cause damage to the files on your disk. Of course, programs that you load onto your computer that are not restricted to running inside a Web browser can do the same thing. Viruses downloaded from Bulletin-Board Systems (BBSs) have long been a problem, but the speed of the Internet amplifies the difficulty. The solution seems to be digital signatures, whereby code is verified to show who the author is. This is based on the idea that a virus works because its creator can be anonymous, so if you remove the anonymity individuals will be forced to be responsible for their actions. This seems like a good plan because it allows programs to be much more functional, and I suspect it will eliminate malicious mischief. If, however, a program has an unintentional bug that's destructive it will still cause problems. The Java approach is to prevent these problems from occurring, via the sandbox. The Java interpreter that lives on your local Web browser examines the applet for any untoward instructions as the applet is being loaded. In particular, the applet cannot write files to disk or erase files (one of the mainstays of the virus). Applets are generally considered to be safe, and since this is essential for reliable client-server systems, any bugs that allow viruses are rapidly repaired. For example, you may want to build a local database or save data for later use offline. The solution is the signed applet that uses public-key encryption to verify that an applet does indeed come from where it claims it does. A signed applet can then go ahead and trash your disk, but the theory is that since you can now hold the applet creator accountable they won't do vicious things. Java 1.1 provides a framework for digital signatures so that you will eventually be able to allow an applet to step outside the sandbox if necessary. Digital signatures have missed an important issue, which is the speed that people move around on the Internet. If you download a buggy program and it does something untoward, how long will it be before you discover the damage? It could be days or even weeks. And by then, how will you track down the program that's done it (and what good will it do at that point?). Internet vs. When Web technology is used for an information network that is restricted to a particular company, it is referred to as an Intranet. Intranets provide much greater security than the Internet, since you can physically control access to the servers within your company. The security problem brings us to one of the divisions that seems to be automatically forming in the world of client-side programming. If your program is running on the Internet, you don't know what platform it will be working under and you want to be extra careful that you don't disseminate buggy code. You need something cross-platform and secure, like a scripting language or Java. If you're running on an Intranet, you might have a different set of constraints. On an Intranet, you're responsible for the quality of your own code and can repair bugs when they're discovered. The time wasted in installing upgrades is the most compelling reason to move to browsers because upgrades are invisible and automatic. If you are involved in such an Intranet, the most sensible approach to take is ActiveX rather than trying to recode your programs in a new language. When faced with this bewildering array of solutions to the client-side programming problem, the best plan of attack is a cost-benefit analysis. Consider the constraints of your problem and what would be the fastest way to get to your solution. Since client-side programming is still programming, it's always a good idea to take the fastest development approach for your particular situation. This is an aggressive stance to prepare for inevitable encounters with the problems of program development. Server-side programming This whole discussion has ignored the issue of server-side programming. What happens when you make a request of a server? Most of the time the request is simply send me this file. Your browser then interprets the file in some appropriate fashion: as an HTML page, a graphic image, a Java applet, a script program, etc. A more complicated request to a server generally involves a database transaction. A common scenario involves a request for a complex database search, which the server then formats into an HTML page and sends to you as the result. These database requests must be processed via some code on the server side, which is generally referred to as server-side programming. Traditionally, server-side programming has been performed using Perl and CGI scripts, but more sophisticated systems have been appearing. These include Java-based Web servers that allow you to perform all your server-side programming in Java by writing what are called servlets. A separate arena: applications Most of the brouhaha over Java has been about applets. Java is actually a general-purpose programming language that can solve any type of problem, at least in theory. Here, Java's strength is not only in its portability, but also its programmability. As you'll see throughout this book, Java has many features that allow you to create robust programs in a shorter period than with previous programming languages. Be aware that this is a mixed blessing. You pay for the improvements through slower execution speed (although there is significant work going on in this area). Like any language, Java has built-in limitations that might make it inappropriate to solve certain types of programming problems. Java is a rapidly-evolving language, however, and as each new release comes out it becomes more and more attractive for solving larger sets of problems. Analysis and Design The object-oriented paradigm is a new and different way of thinking about programming and many folks have trouble at first knowing how to approach a project. Now that you know that everything is supposed to be an object, you can create a good design, one that will take advantage of all the benefits that OOP has to offer. Books on OOP analysis and design are coming out of the woodwork. So, hoping I've built a healthy skepticism within you, I shall endeavor to give you my own perspective on analysis and design in as few paragraphs as possible. Staying on course While you're going through the development process, the most important issue is this: don't get lost. It's easy to do. Most of these methodologies are designed to solve the largest of problems. But some sort of process, no matter how limited, will generally get you on your way in a much better fashion than simply beginning to code. That said, if you're looking at a methodology that contains tremendous detail and suggests many steps and documents, it's still difficult to know when to stop. Keep in mind what you're trying to discover: 1. What are the objects? For various reasons you might need more descriptions and documents than this, but you can't really get away with any less. The process can be undertaken in four phases, and a phase 0 which is just the initial commitment to using some kind of structure. Phase 0: Let's make a plan The first step is to decide what steps you're going to have in your process. It sounds simple (in fact, all of this sounds simple) and yet, often, people don't even get around to phase one before they start coding. If your plan is let's jump in and start coding, fine. You might also decide at this phase that some additional process structure is necessary but not the whole nine yards. When I began to study story structure (so that I will someday write a novel) I was initially resistant to the idea, feeling that when I wrote I simply let it flow onto the page. What I found was that when I wrote about computers the structure was simple enough so I didn't need to think much about it, but I was still structuring my work, albeit only semi-consciously in my head. So even if you think that your plan is to just start coding, you still go through the following phases while asking and answering certain questions. Phase 1: What are we making? Their intention was good, however. The system specification is a top-level exploration into the problem and in some sense a discovery of whether it can be done and how long it will take. Since both of these will require consensus among people, I think it's best to keep them as bare as possible - ideally, to lists and basic diagrams - to save time. You might have other constraints that require you to expand them into bigger documents. It's necessary to stay focused on the heart of what you're trying to accomplish in this phase: determine what the system is supposed to do. You try to discover a full set of use-cases for your system, and once you've done that you've got the core of what the system is supposed to do. The nice thing about focusing on use-cases is that they always bring you back to the essentials and keep you from drifting off into issues that aren't critical for getting the job done. That is, if you have a full set of use-cases you can describe your system and move onto the next phase. You probably won't get it all figured out perfectly at this phase, but that's OK. Everything will reveal itself in the fullness of time, and if you demand a perfect system specification at this point you'll get stuck. It helps to kick-start this phase by describing the system in a few paragraphs and then looking for nouns and verbs. The nouns become the objects and the verbs become the methods in the object interfaces. You'll be surprised at how useful a tool this can be; sometimes it will accomplish the lion's share of the work for you. Although it's a black art, at this point some kind of scheduling can be quite useful. You now have an overview of what you're building so you'll probably be able to get some idea of how long it will take. A lot of factors come into play here: if you estimate a long schedule then the company might not decide to build it, or a manager might have already decided how long the project should take and will try to influence your estimate. But it's best to have an honest schedule from the beginning and deal with the tough decisions early. There have been a lot of attempts to come up with accurate scheduling techniques (like techniques to predict the stock market), but probably the best approach is to rely on your experience and intuition. Get a gut feeling for how long it will really take, then double that and add 10 percent. Your gut feeling is probably correct; you can get something working in that time. The doubling will turn that into something decent, and the 10 percent will deal with final polishing and details. However you want to explain it, and regardless of the moans and manipulations that happen when you reveal such a schedule, it just seems to work out that way. Phase 2: How will we build it? In this phase you must come up with a design that describes what the classes look like and how they will interact. A useful diagramming tool that has evolved over time is the Unified Modeling Language (UML). You can get the specification for UML at www.rational.com. UML can also be helpful as a descriptive tool during phase 1, and some of the diagrams you create there will probably show up unmodified in phase 2. You don't need to use UML, but it can be helpful, especially if you want to put a diagram up on the wall for everyone to ponder, which is a good idea. An alternative to UML is a textual description of the objects and their interfaces (as I described in Thinking in C++), but this can be limiting. The most successful consulting experiences I've had when coming up with an initial design involves standing in front of a team, who hadn't built an OOP project before, and drawing objects on a whiteboard. We talked about how the objects should communicate with each other, and erased some of them and replaced them with other objects. The team (who knew what the project was supposed to do) actually created the design; they owned the design rather than having it given to them. All I was doing was guiding the process by asking the right questions, trying out the assumptions and taking the feedback from the team to modify those assumptions. The true beauty of the process was that the team learned how to do object-oriented design not by reviewing abstract examples, but by working on the one design that was most interesting to them at that moment: theirs. You'll know you're done with phase 2 when you have described the objects and their interfaces. Well, most of them - there are usually a few that slip through the cracks and don't make themselves known until phase 3. But that's OK. All you are concerned with is that you eventually discover all of your objects. It's nice to discover them early in the process but OOP provides enough structure so that it's not so bad if you discover them later. Phase 3: Let's build it! If you're reading this book you're probably a programmer, so now we're at the part you've been trying to get to. Getting code to run and do what you want is fulfilling, even like some kind of drug if you look at the obsessive behavior of some programmers. But it's my experience that coming up with an elegant solution is deeply satisfying at an entirely different level; it feels closer to art than technology. And elegance always pays off; it's not a frivolous pursuit. Not only does it give you a program that's easier to build and debug, but it's also easier to understand and maintain, and that's where the financial value lies. After you build the system and get it running, it's important to do a reality check, and here's where the requirements analysis and system specification comes in. Go through your program and make sure that all the requirements are checked off, and that all the use-cases work the way they're described. Now you're done. Or are you? Perhaps there's a better term to describe what's going on. The term is iteration. That is, You won't get it right the first time, so give yourself the latitude to learn and to go back and make changes. You might need to make a lot of changes as you learn and understand the problem more deeply. The elegance you'll produce if you iterate until you've got it right will pay off, both in the short and the long run. What it means to get it right isn't just that the program works according to the requirements and the use-cases. It also means that the internal structure of the code makes sense to you, and feels like it fits together well, with no awkward syntax, oversized objects or ungainly exposed bits of code. In addition, you must have some sense that the program structure will survive the changes that it will inevitably go through during its lifetime, and that those changes can be made easily and cleanly. This is no small feat. You must not only understand what you're building, but also how the program will evolve (what I call the vector of change). Fortunately, object-oriented programming languages are particularly adept at supporting this kind of continuing modification - the boundaries created by the objects are what tend to keep the structure from breaking down. They are also what allow you to make changes that would seem drastic in a procedural program without causing earthquakes throughout your code. In fact, support for iteration might be the most important benefit of OOP. With iteration, you create something that at least approximates what you think you're building, and then you kick the tires, compare it to your requirements and see where it falls short. When you see the system, you realize you want to solve a different problem. If you think this kind of iteration is going to happen, then you owe it to yourself to build your first version as quickly as possible so you can find out if it's what you want. Iteration is closely tied to incremental development. Incremental development means that you start with the core of your system and implement it as a framework upon which to build the rest of the system piece by piece. Then you start adding features one at a time. The trick to this is in designing a framework that will accommodate all the features you plan to add to it. Also, new features that are incorporated later in the development or maintenance phases can be added more easily. OOP supports incremental development because if your program is designed well, your increments will turn out to be discreet objects or groups of objects. Plans pay off Of course you wouldn't build a house without a lot of carefully-drawn plans. If you build a deck or a dog house, your plans won't be so elaborate but you'll still probably start with some kind of sketches to guide you on your way. Software development has gone to extremes. For a long time, people didn't have much structure in their development, but then big projects began failing. In reaction, we ended up with methodologies that had an intimidating amount of structure and detail. These were too scary to use - it looked like you'd spend all your time writing documents and no time programming. Use an approach that fits your needs (and your personality). No matter how minimal you choose to make it, some kind of plan will make a big improvement in your project as opposed to no plan at all. Remember that, by some estimates, over 50 percent of projects fail. Java vs. Java looks a lot like C++, and so naturally it would seem that C++ will be replaced by Java. But I'm starting to question this logic. Also, there seems to be a perking interest in C++ in many fields, so I don't think that language is going away any time soon. Certainly it has been adapted in a number of ways to solve particular problems, especially with tools like Microsoft Visual C++ and Borland C++ Builder (a particular favorite of mine). These combine libraries, component models and code generation tools to solve the problem of developing windowed end-user applications (for Microsoft Windows). And yet, what do the vast majority of Windows developers use? Microsoft's Visual Basic (VB). This despite the fact that VB produces the kind of code that becomes unmanageable when the program is only a few pages long (and syntax that can be positively mystifying). As successful and popular as VB is, from a language design viewpoint it's a mountain of hacks. It would be nice to have the ease and power of VB without the resulting unmanageable code. Add to this the fact that Java has the most robust type checking and error-handling systems I've ever seen in a language and you have the makings of a significant leap forward in programming productivity. Should you use Java instead of C++ for your project? Other than Web applets, there are two issues to consider. First, if you want to use a lot of existing libraries (and you'll certainly get a lot of productivity gains there), or if you have an existing C or C++ code base, Java might slow your development down rather than speeding it up. If you're developing all your code primarily from scratch, then the simplicity of Java over C++ will shorten your development time. The biggest issue is speed. Interpreted Java has been slow, even 20 to 50 times slower than C in the original Java interpreters. This has improved quite a bit over time, but it will still remain an important number. Computers are about speed; if it wasn't significantly faster to do something on a computer then you'd do it by hand. Because C++ is a superset of the C language, it includes many of that language's undesirable features which can make some aspects of C++ overly complicated. The Java language assumes that you want to do only object-oriented programming. This means that before you can begin you must shift your mindset into an object-oriented world (unless it's already there). The benefit of this initial effort is the ability to program in a language that is simpler to learn and to use than many other OOP languages. In this chapter we'll see the basic components of a Java program and we'll learn that everything in Java is an object, even a Java program. You manipulate objects with handles Each programming language has its own means of manipulating data. Sometimes the programmer must constantly be aware of what type of manipulation is going on. Are you manipulating the object directly or are you dealing with some kind of indirect representation (a pointer in C or C++) that must be treated with a special syntax? All this is simplified in Java. You treat everything as an object, so there is a single consistent syntax that you use everywhere. Although you treat everything as an object, the identifier you manipulate is actually a handle to an object. As long as you're holding this handle, you have a connection to the television, but when someone says change the channel or lower the volume, what you're manipulating is the handle, which in turn modifies the object. Also, the remote control can stand on its own, with no television. That is, just because you have a handle doesn't mean there's necessarily an object connected to it. So if you want to hold a word or sentence, you create a String handle: String s; But here you've created only the handle, not an object. If you decided to send a message to s at this point, you'll get an error (at run-time) because s isn't actually attached to anything (there's no television). A safer practice, then, is always to initialize a handle when you create it: String s = asdf; However, this uses a special case: strings can be initialized with quoted text. Normally, you must use a more general type of initialization for objects. You must create all the objects When you create a handle, you want to connect it with a new object. You do so, in general, with the new keyword. Of course, String is not the only type that exists. Java comes with a plethora of ready-made types. What's more important is that you can create your own types. In fact, that's the fundamental activity in Java programming, and it's what you'll be learning about in the rest of this book. Where storage lives It's useful to visualize some aspects of how things are laid out while the program is running, in particular how memory is arranged. There are six different places to store data: 1. Registers. This is the fastest storage because it exists in a place different than that of other storage: inside the processor. However, the number of registers is severely limited, so registers are allocated by the compiler according to its needs. You don't have direct control, nor do you see any evidence in your programs that registers even exist. 2. The stack. This lives in the general RAM (random-access memory) area, but has direct support from the processor via its stack pointer. The stack pointer is moved down to create new memory and moved up to release that memory. This is an extremely fast and efficient way to allocate storage, second only to registers. The Java compiler must know, while it is creating the program, the exact size and lifetime of all the data that is stored on the stack, because it must generate the code to move the stack pointer up and down. This constraint places limits on the flexibility of your programs, so while some Java storage exists on the stack - in particular, object handles - Java objects are not placed on the stack. 3. The heap. This is a general-purpose pool of memory (also in the RAM area) where all Java objects live. The nice thing about the heap is that, unlike the stack, the compiler doesn't need to know how much storage it needs to allocate from the heap or how long that storage must stay on the heap. Thus, there's a great deal of flexibility in using storage on the heap. Whenever you need to create an object, you simply write the code to create it using new and the storage is allocated on the heap when that code is executed. And of course there's a price you pay for this flexibility: it takes more time to allocate heap storage. 4. Static storage. Static is used here in the sense of in a fixed location (although it's also in RAM). Static storage contains data that is available for the entire time a program is running. You can use the static keyword to specify that a particular element of an object is static, but Java objects themselves are never placed in static storage. 5. Constant storage. Constant values are often placed directly in the program code, which is safe since they can never change. Sometimes constants are cordoned off by themselves so that they can be optionally placed in read-only memory (ROM). 6. Non-RAM storage. If data lives completely outside a program it can exist while the program is not running, outside the control of the program. The trick with these types of storage is turning the objects into something that can exist on the other medium, and yet can be resurrected into a regular RAM-based object when necessary. Java 1.1 provides support for lightweight persistence, and future versions of Java might provide more complete solutions for persistence. Special case: primitive types There is a group of types that gets special treatment; you can think of these as primitive types that you use quite often in your programming. The reason for the special treatment is that to create an object with new, especially a small, simple variable, isn't very efficient because new places objects on the heap. For these types Java falls back on the approach taken by C and C++. That is, instead of creating the variable using new, an automatic variable is created that is not a handle. The variable holds the value, and it's placed on the stack so it's much more efficient. Java determines the size of each primitive type. These sizes don't change from one machine architecture to another as they do in most languages. This size invariance is one reason Java programs are so portable. The primitive data types also have wrapper classes for them. That means that if you want to make a non-primitive object on the heap to represent that primitive type, you use the associated wrapper. For example: char c = 'x'; Character C = new Character(c); or you could also use: Character C = new Character('x'); The reasons for doing this will be shown in a later chapter. High-precision numbers Java 1.1 has added two classes for performing high-precision arithmetic: BigInteger and BigDecimal. Although these approximately fit into the same category as the wrapper classes, neither one has a primitive analogue. Both classes have methods that provide analogues for the operations that you perform on primitive types. That is, you can do anything with a BigInteger or BigDecimal that you can with an int or float, it's just that you must use method calls instead of operators. Also, since there's more involved, the operations will be slower. You're exchanging speed for accuracy. BigInteger supports arbitrary-precision integers. This means that you can accurately represent integral values of any size without losing any information during operations. BigDecimal is for arbitrary-precision fixed-point numbers; you can use these for accurate monetary calculations, for example. Consult your online documentation for details about the constructors and methods you can call for these two classes. Arrays in Java Virtually all programming languages support arrays. Using arrays in C and C++ is perilous because those arrays are only blocks of memory. A Java array is guaranteed to be initialized and cannot be accessed outside of its range. The range checking comes at the price of having a small amount of memory overhead on each array as well as verifying the index at run time, but the assumption is that the safety and increased productivity is worth the expense. When you create an array of objects, you are really creating an array of handles, and each of those handles is automatically initialized to a special value with its own keyword: null. When Java sees null, it recognizes that the handle in question isn't pointing to an object. You must assign an object to each handle before you use it, and if you try to use a handle that's still null, the problem will be reported at run-time. Thus, typical array errors are prevented in Java. You can also create an array of primitives. Again, the compiler guarantees initialization because it zeroes the memory for that array. Arrays will be covered in detail in later chapters. You never need to destroy an object In most programming languages, the concept of the lifetime of a variable occupies a significant portion of the programming effort. How long does the variable last? If you are supposed to destroy it, when should you? Confusion over variable lifetimes can lead to a lot of bugs, and this section shows how Java greatly simplifies the issue by doing all the cleanup work for you. Scoping Most procedural languages have the concept of scope. This determines both the visibility and lifetime of the names defined within that scope. Indentation makes Java code easier to read. Since Java is a free form language, the extra spaces, tabs and carriage returns do not affect the resulting program. Thus the C and C++ ability to hide a variable in a larger scope is not allowed because the Java designers thought that it led to confusing programs. Scope of objects Java objects do not have the same lifetimes as primitives. When you create a Java object using new, it hangs around past the end of the scope. However, the String object that s was pointing to is still occupying memory. In this bit of code, there is no way to access the object because the only handle to it is out of scope. In later chapters you'll see how the handle to the object can be passed around and duplicated during the course of a program. It turns out that because objects created with new stay around for as long as you want them, a whole slew of programming problems simply vanish in C++ and Java. The hardest problems seem to occur in C++ because you don't get any help from the language in making sure that the objects are available when they're needed. And more importantly, in C++ you must make sure that you destroy the objects when you're done with them. That brings up an interesting question. If Java leaves the objects lying around, what keeps them from filling up memory and halting your program? This is exactly the kind of problem that would occur in C++. This is where a bit of magic happens. Java has a garbage collector, which looks at all the objects that were created with new and figures out which ones are not being referenced anymore. Then it releases the memory for those objects, so the memory can be used for new objects. This means that you never need to worry about reclaiming memory yourself. You simply create objects, and when you no longer need them they will go away by themselves. This eliminates a certain class of programming problem: the so-called memory leak, in which a programmer forgets to release memory. Creating new data types: class If everything is an object, what determines how a particular class of object looks and behaves? Put another way, what establishes the type of an object? You might expect there to be a keyword called type and that certainly would have made sense. In fact, you cannot tell it to do much of anything (that is, you cannot send it any interesting messages) until you define some methods for it. A data member is an object (that you communicate with via its handle) of any type. It can also be one of the primitive types (which isn't a handle). If it is a handle to an object, you must initialize that handle to connect it to an actual object (using new, as seen earlier) in a special function called a constructor (described fully in Chapter 4). If it is a primitive type you can initialize it directly at the point of definition in the class. This is accomplished by stating the name of the object handle, followed by a period (dot), followed by the name of the member inside the object (objectHandle.member). For example: d.i = 47; d.f = 1.1f; d.b = false; It is also possible that your object might contain other objects that contain data you'd like to modify. For this, you just keep connecting the dots. For example: myPlane.leftTank.capacity = 100; The DataOnly class cannot do much of anything except hold data, because it has no member functions (methods). To understand how those work, you must first understand arguments and return values, which will be described shortly. This ensures that member variables of primitive types will always be initialized (something C++ doesn't do), reducing a source of bugs. However, this guarantee doesn't apply to local variables - those that are not fields of a class. Thus, if within a function definition you have: int x; Then x will get some random value (as in C and C++); it will not automatically be initialized to zero. You are responsible for assigning an appropriate value before you use x. If you forget, Java definitely improves on C++: you get a compile-time error telling you the variable might not have been initialized. The term that is more commonly used in Java is method, as in a way to do something. If you want, you can continue thinking in terms of functions. It's really only a syntactic difference, but from now on method will be used in this book rather than function. Methods in Java determine the messages an object can receive. In this section you will learn how simple it is to define a method. The fundamental parts of a method are the name, the arguments, the return type, and the body. The method name, as you might imagine, identifies the method. The argument list gives the types and names for the information you want to pass into the method. Methods in Java can be created only as part of a class. A method can be called only for an object,3 and that object must be able to perform that method call. If you try to call the wrong method for an object, you'll get an error message at compile time. You call a method for an object by naming the object followed by a period (dot), followed by the name of the method and its argument list, like this: objectName.methodName(arg1, arg2, arg3). For example, suppose you have a method f( ) that takes no arguments and returns a value of type int. Then, if you have an object called a for which f( ) can be called, you can say this: int x = a.f(); The type of the return value must be compatible with the type of x. This act of calling a method is commonly referred to as sending a message to an object. In the above example, the message is f( ) and the object is a. Object-oriented programming is often summarized as simply sending messages to objects. The argument list The method argument list specifies what information you pass into the method. As you might guess, this information - like everything else in Java - takes the form of objects. So, what you must specify in the argument list are the types of the objects to pass in and the name to use for each one. As in any situation in Java where you seem to be handing objects around, you are actually passing handles.4 The type of the handle must be correct, however. If the argument is supposed to be a String, what you pass in must be a string. Consider a method that takes a string as its argument. Once s is passed into the method, you can treat it just like any other object. You can also see the use of the return keyword, which does two things. First, it means leave the method, I'm done. Second, if the method produces a value, that value is placed right after the return statement. In this case, the return value is produced by evaluating the expression s.length( ) * 2. You can return any type you want, but if you don't want to return anything at all, you do so by indicating that the method returns void. You can return from a method at any point, but if you've given a non-void return type then the compiler will ensure that you return the appropriate type of value regardless of where you return. At this point, it can look like a program is just a bunch of objects with methods that take other objects as arguments and send messages to those other objects. That is indeed much of what goes on, but in the following chapter you'll learn how to do the detailed low-level work by making decisions within a method. For this chapter, sending messages will suffice. Building a Java program There are several other issues you must understand before seeing your first Java program. Name visibility A problem in any programming language is the control of names. If you use a name in one module of the program, and another programmer uses the same name in another module, how do you distinguish one name from another and prevent the two names from clashing? In C this is a particular problem because a program is often an unmanageable sea of names. C++ classes (on which Java classes are based) nest functions within classes so they cannot clash with function names nested within other classes. However, C++ still allowed global data and global functions, so clashing was still possible. To solve this problem, C++ introduced namespaces using additional keywords. Java was able to avoid all of this by taking a fresh approach. To produce an unambiguous name for a library, the specifier used is not unlike an Internet domain name. In fact, the Java creators want you to use your Internet domain name in reverse since those are guaranteed to be unique. Since my domain name is BruceEckel.com, my utility library of foibles would be named com.bruceeckel.utility.foibles. After your reversed domain name, the dots are intended to represent subdirectories. In Java 1.0 and Java 1.1 the domain extension com, edu, org, net, etc., was capitalized by convention, so the library would appear: COM.bruceeckel.utility.foibles. Partway through the development of Java 1.2, however, it was discovered that this caused problems and so now the entire package name is lowercase. This mechanism in Java means that all of your files automatically live in their own namespaces, and each class within a file automatically has a unique identifier. Using other components Whenever you want to use a predefined class in your program, the compiler must know how to locate it. Of course, the class might already exist in the same source code file that it's being called from. In that case, you simply use the class - even if the class doesn't get defined until later in the file. Java eliminates the forward referencing problem so you don't need to think about it. What about a class that exists in some other file? You might think that the compiler should be smart enough to simply go and find it, but there is a problem. Imagine that you want to use a class of a particular name, but the definition for that class exists in more than one file. Or worse, imagine that you're writing a program, and as you're building it you add a new class to your library that conflicts with the name of an existing class. To solve this problem, you must eliminate all potential ambiguities. This is accomplished by telling the Java compiler exactly what classes you want using the import keyword. With these, you don't need to worry about long, reversed domain names; you just say, for example: import java.util.Vector; to tell the compiler that you want to use Java's Vector class. However, util contains a number of classes and you might want to use several of them without declaring them all explicitly. This is easily accomplished by using '*' to indicate a wildcard: import java.util.*; It is more common to import a collection of classes in this manner than to import classes individually. The static keyword Ordinarily, when you create a class you are describing how objects of that class look and how they will behave. You don't actually get anything until you create an object of that class with new, and at that point data storage is created and methods become available. But there are two situations in which this approach is not sufficient. One is if you want to have only one piece of storage for a particular piece of data, regardless of how many objects are created, or even if no objects are created. The other is if you need a method that isn't associated with any particular object of this class. That is, you need a method that you can call even if no objects are created. You can achieve both of these effects with the static keyword. When you say something is static, it means that data or method is not tied to any particular object instance of that class. So even if you've never created an object of that class you can call a static method or access a piece of static data. With ordinary, non-static data and methods you must create an object and use that object to access the data or method, since non-static data and methods must know the particular object they are working with. Some object-oriented languages use the terms class data and class methods, meaning that the data and methods exist only for the class as a whole, and not for any particular objects of the class. Sometimes the Java literature uses these terms too. To make a data member or method static, you simply place the keyword before the definition. Consider: StaticTest st1 = new StaticTest(); StaticTest st2 = new StaticTest(); At this point, both st1.i and st2.i have the same value of 47 since they refer to the same piece of memory. There are two ways to refer to a static variable. As indicated above, you can name it via an object, by saying, for example, st2.i. You can also refer to it directly through its class name, something you cannot do with a non-static member. At this point, both st1.i and st2.i will have the value 48. Similar logic applies to static methods. You can refer to a static method either through an object as you can with any method, or with the special additional syntax classname.method( ). An important use of static for methods is to allow you to call that method without creating an object. This is essential, as we will see, in defining the main( ) method that is the entry point for running an application. Like any method, a static method can create or use named objects of its type, so a static method is often used as a shepherd for a flock of instances of its own type. Your first Java program Finally, here's the program.5 It prints out information about the system that it's running on using various methods of the System object from the Java standard library. Note that it is extra. That's because there's a certain library of classes that are automatically brought into every Java file: java.lang. Start up your Web browser and look at the documentation from Sun. If you look at the packages.html file, you'll see a list of all the different class libraries that come with Java. Select java.lang. Under Class Index you'll see a list of all the classes that are part of that library. Since java.lang is implicitly included in every Java code file, these classes are automatically available. In the list, you'll see System and Runtime, which are used in Property.java. There's no Date class listed in java.lang, which means you must import another library to use that. If you don't know the library where a particular class is, or if you want to see all of the classes, you can select Class Hierarchy in the Java documentation. In a Web browser, this takes awhile to construct, but you can find every single class that comes with Java. Then you can use the browser's find function to find Date. When you do you'll see it listed as java.util.Date, which lets you know that it's in the util library and that you must import java.util.* in order to use Date. If you look at the documentation starting from the packages.html file (which I've set in my Web browser as the default starting page), select java.lang and then System. You'll see that the System class has several fields, and if you select out you'll discover that it's a static PrintStream object. Since it's static you don't need to create anything. The out object is always there and you can just use it. What you can do with this out object is determined by the type it is: a PrintStream. Conveniently, PrintStream is shown in the description as a hyperlink, so if you click on that you'll see a list of all the methods you can call for PrintStream. There are quite a few and these will be covered later in the book. The name of the class is the same as the name of the file. When you're creating a stand-alone program such as this one, one of the classes in the file must have the same name as the file. The argument to main( ) is an array of String objects. The args won't be used in this program, but they need to be there because they hold the arguments invoked on the command line. The first line of the program is quite interesting: System.out.println(new Date()); Consider the argument: a Date object is being created just to send its value to println( ). As soon as this statement is finished, that Date is unnecessary, and the garbage collector can come along and get it anytime. We don't need to worry about cleaning it up. The second line calls System.getProperties( ). If you consult the online documentation using your Web browser, you'll see that getProperties( ) is a static method of class System. Because it's static, you don't need to create any objects in order to call the method; a static method is always available whether an object of its class exists or not. When you call getProperties( ), it produces the system properties as an object of class Properties. The handle that comes back is stored in a Properties handle called p. In line three, you can see that the Properties object has a method called list( ) that sends its entire contents to a PrintStream object that you pass as an argument. The fourth and sixth lines in main( ) are typical print statements. Note that to print multiple String values, we simply separate them with '+' signs. However, there's something strange going on here. The '+' sign doesn't mean addition when it's used with String objects. Normally, you wouldn't ascribe any meaning to '+' when you think of strings. However, the Java String class is blessed with something called operator overloading. That is, the '+' sign, only when used with String objects, behaves differently from the way it does with everything else. For Strings, it means concatenate these two strings. But that's not all. If you look at the statement: System.out.println(Total Memory = + rt.totalMemory() + Free Memory = + rt.freeMemory()); totalMemory( ) and freeMemory( ) return numerical values, and not String objects. What happens when you add a numerical value to a String? The compiler sees the problem and magically calls a method that turns that numerical value (int, float, etc.) into a String, which can then be added with the plus sign. This automatic type conversion also falls into the category of operator overloading. Much of the Java literature states vehemently that operator overloading (a feature in C++) is bad, and yet here it is! However, this is wired into the compiler, only for String objects, and you can't overload operators for any of the code you write. The fifth line in main( ) creates a Runtime object by calling the static method getRuntime( ) for the class Runtime. What's returned is a handle to a Runtime object; whether this is a static object or one created with new doesn't need to concern you, since you can use the objects without worrying about who's responsible for cleaning them up. As shown, the Runtime object can tell you information about memory usage. Comments and embedded documentation There are two types of comments in Java. This type of comment is convenient and commonly used because it's easy. Possibly the biggest problem with documenting code has been maintaining that documentation. If the documentation and the code are separate, it becomes a hassle to change the documentation every time you change the code. The solution seems simple: link the code to the documentation. The easiest way to do this is to put everything in the same file. To complete the picture, however, you need a special comment syntax to mark special documentation and a tool to extract those comments and put them in a useful form. This is what Java has done. The tool to extract the comments is called javadoc. It uses some of the technology from the Java compiler to look for special comment tags you put in your programs. It not only extracts the information marked by these tags, but it also pulls out the class name or method name that adjoins the comment. This way you can get away with the minimal amount of work to generate decent program documentation. The output of javadoc is an HTML file that you can view with your Web browser. This tool allows you to create and maintain a single source file and automatically generate useful documentation. Because of javadoc we have a standard for creating documentation, and it's easy enough that we can expect or even demand documentation with all Java libraries. There are two primary ways to use javadoc: embed HTML, or use doc tags. Doc tags are commands that start with a '@' and are placed at the beginning of a comment line. That is, a class comment appears right before the definition of a class; a variable comment appears right in front of the definition of a variable and a method comment appears right in front of the definition of a method. Comments for private and friendly (see Chapter 5) members are ignored and you'll see no output. However, all class comments are included in the output. The output for the above code is an HTML file that has the same standard format as all the rest of the Java documentation, so users will be comfortable with the format and can easily navigate your classes. It's worth entering the above code, sending it through javadoc and viewing the resulting HTML file to see the results. Embedded HTML Javadoc passes HTML commands through to the generated HTML document. Javadoc reformats everything so that it conforms to the standard documentation appearance. All types of comment documentation - class, variable, and method - can support embedded HTML. Javadoc will generate HTML with the @see tags hyperlinked to the other documentation. The forms are: @see classname @see fully-qualified-classname @see fully-qualified-classname#method-name Each one adds a hyperlinked See Also entry to the generated documentation. Javadoc will not check the hyperlinks you give it to make sure they are valid. Class documentation tags Along with embedded HTML and @see references, class documentation can include tags for version information and the author's name. Class documentation can also be used for interfaces (described later in the book). When the -version flag is placed on the javadoc command line, the version information will be called out specially in the generated HTML documentation. When the -author flag is placed on the javadoc command line, the author information will be called out specially in the generated HTML documentation. You can have multiple author tags for a list of authors, but they must be placed consecutively. All the author information will be lumped together into a single paragraph in the generated HTML. Variable documentation tags Variable documentation can include only embedded HTML and @see references. Method documentation tags As well as embedded documentation and @see references, methods allow documentation tags for parameters, return values, and exceptions. The description is considered finished when a new documentation tag is encountered. You can have any number of these, presumably one for each parameter. It can continue on subsequent lines. Although only one exception object can emerge when you call a method, a particular method might produce any number of different types of exceptions, all of which need descriptions. The deprecated tag is a suggestion that you no longer use this particular feature, since sometime in the future it is likely to be removed. Methods that are marked @deprecated cause the compiler to issue warnings if it is used. The last line also finishes with a comment, and this one indicates the end of the source code listing, which allows it to be automatically extracted from the text of the book and checked with a compiler. This is described in detail in Chapter 17. Coding style The unofficial standard in Java is to capitalize the first letter of a class name. Summary In this chapter you have seen enough of Java programming to understand how to write a simple program, and you have gotten an overview of the language and some of its basic ideas. However, the examples so far have all been of the form do this, then do that, then do something else. What if you want the program to make choices, such as if the result of doing this is red, do that, if not, then do something else? The support in Java for this fundamental programming activity will be covered in the next chapter. Exercises 1. Following the first example in this chapter, create a Hello, World program that simply prints out that statement. You need only a single method in your class (the main one that gets executed when the program starts). Remember to make it static and to put the argument list in, even though you don't use the argument list. Compile the program with javac and run it using java. 2. Write a program that prints three arguments taken from the command line. 3. Find the code for the second version of Property.java, which is the simple comment documentation example. Execute javadoc on the file and view the results with your Web browser. 4. Take the program in Exercise 1 and add comment documentation to it. Extract this comment documentation into an HTML file using javadoc and view it with your Web browser. 4. ? 3: Controlling program flow Like a sentient creature, a program must manipulate its world and make choices during execution. In Java you manipulate objects and data using operators, and you make choices with execution control statements. Java was inherited from C++, so most of these statements and operators will be familiar to C and C++ programmers. Java has also added some improvements and simplifications. Using Java operators An operator takes one or more arguments and produces a new value. The arguments are in a different form than ordinary method calls, but the effect is the same. You should be reasonably comfortable with the general concept of operators from your previous programming experience. All operators produce a value from their operands. In addition, an operator can change the value of an operand. This is called a side effect. The most common use for operators that modify their operands is to generate the side effect, but you should keep in mind that the value produced is available for your use just as in operators without side effects. Almost all operators work only with primitives. The exceptions are '=', '==' and '!=', which work with all objects (and are a point of confusion for objects). In addition, the String class supports '+' and '+='. Precedence Operator precedence defines how an expression evaluates when several operators are present. Java has specific rules that determine the order of evaluation. The easiest one to remember is that multiplication and division happen before addition and subtraction. Programmers often forget the other precedence rules, so you should use parentheses to make the order of evaluation explicit. An rvalue is any constant, variable or expression that can produce a value, but an lvalue must be a distinct, named variable. Since the primitive holds the actual value and not a handle to an object, when you assign primitives you copy the contents from one place to another. For example, if you say A = B for primitives, then the contents of B is copied into A. If you then go on to modify A, B is naturally unaffected by this modification. This is what you've come to expect as a programmer for most situations. When you assign objects, however, things change. Whenever you manipulate an object, what you're manipulating is the handle, so when you assign from one object to another you're actually copying a handle from one place to another. This means that if you say C = D for objects, you end up with both C and D pointing to the object that, originally, only D pointed to. The following example will demonstrate this. As an aside, the first thing you see is a package statement for package c03, indicating this book's Chapter 3. The first code listing of each chapter will contain a package statement like this to establish the chapter number for the remaining code listings in that chapter. In Chapter 17, you'll see that as a result, all the listings in chapter 3 (except those that have different package names) will be automatically placed in a subdirectory called c03, Chapter 4's listings will be in c04 and so on. All this will happen via the CodePackager.java program shown in Chapter 17, and in Chapter 5 the concept of packages will be fully explained. What you need to recognize at this point is that, for this book, lines of code of the form package c03 are used just to establish the chapter subdirectory for the listings in the chapter. In order to run the program, you must ensure that the classpath contains the root directory where you installed the source code for this book. In this case, the command line is: java c03.Assignment Keep this in mind any time you're running a program that's in a package. The i value within each Number is given a different value, and then n2 is assigned to n1, and n1 is changed. This is because both n1 and n2 contain the same handle, which is pointing to the same object. But what if you don't want aliasing to occur in this case? This is a non-trivial topic, so it is left for Chapter 12, which is devoted to aliasing. In the meantime, you should keep in mind that assignment for objects can add surprises. But once again a handle is being passed so the line y.c = 'z'; is actually changing the object outside of f( ). The output shows this: 1: x.c: a 2: x.c: z Aliasing and its solution is a complex issue and, although you must wait until Chapter 12 for all the answers, you should be aware of it at this point so you can watch for pitfalls. Integer division truncates, rather than rounds, the result. Java also uses a shorthand notation to perform an operation and an assignment at the same time. This is denoted by an operator followed by an equal sign, and is consistent with all the operators in the language (whenever it makes sense). Of course, they all ultimately end up using System.out.println( ). To generate numbers, the program first creates a Random object. Because no arguments are passed during creation, Java uses the current time as a seed for the random number generator. The program generates a number of different types of random numbers with the Random object simply by calling different methods: nextInt( ), nextLong( ), nextFloat( ) or nextDouble( ). The modulus operator, when used with the result of the random number generator, limits the result to an upper bound of the operand minus one (99 in this case). Unary minus and plus operators The unary minus (-) and unary plus (+) are the same operators as binary minus and plus. The compiler figures out which use is intended by the way you write the expression. For instance, the statement x = -a; has an obvious meaning. The compiler is able to figure out: x = a * -b; but the reader might get confused, so it is more clear to say: x = a * (-b); The unary minus produces the negative of the value. Unary plus provides symmetry with unary minus, although it doesn't do much. Auto increment and decrement Java, like C, is full of shortcuts. Shortcuts can make code much easier to type, and either easier or harder to read. Two of the nicer shortcuts are the increment and decrement operators (often referred to as the auto-increment and auto-decrement operators). The decrement operator is -- and means decrease by one unit. The increment operator is ++ and means increase by one unit. If A is an int, for example, the expression ++A is equivalent to (A = A + 1). Increment and decrement operators produce the value of the variable as a result. There are two versions of each type of operator, often called the prefix and postfix versions. Pre-increment means the ++ operator appears before the variable or expression, and post-increment means the ++ operator appears after the variable or expression. Similarly, pre-decrement means the -- operator appears before the variable or expression, and post-decrement means the -- operator appears after the variable or expression. For pre-increment and pre-decrement, (i.e., ++A or --A), the operation is performed and the value is produced. For post-increment and post-decrement (i.e. A++ or A--), the value is produced, then the operation is performed. These are the only operators (other than those involving assignment) that have side effects. As you progress in this book you'll see that many parts are simpler, and yet Java isn't that much easier than C++. Relational operators Relational operators generate a boolean result. They evaluate the relationship between the values of the operands. A relational expression produces true if the relationship is true, and false if the relationship is untrue. Equivalence and nonequivalence works with all built-in data types, but the other comparisons won't work with type boolean. Testing object equivalence The relational operators == and!= also work with all objects, but their meaning often confuses the first-time Java programmer. Surely the output should be true and then false, since both Integer objects are the same. But while the contents of the objects are the same, the handles are not the same and the operators == and!= compare object handles. So the output is actually false and then true. Naturally, this surprises people at first. What if you want to compare the actual contents of an object for equivalence? You must use the special method equals( ) that exists for all objects (not primitives, which work fine with == and!=). Ah, but it's not as simple as that. This is because the default behavior of equals( ) is to compare handles. So unless you override equals( ) in your new class you won't get the desired behavior. Unfortunately, you won't learn about overriding until Chapter 7, but being aware of the way equals( ) behaves might save you some grief in the meantime. Most of the Java library classes implement equals( ) so that it compares the contents of objects instead of their handles. Logical operators The logical operators AND (andand), OR (||) and NOT (!) produce a boolean value of true or false based on the logical relationship of its arguments. The subsequent expressions, however, produce boolean values using relational comparisons, then use logical operations on the results. You can replace the definition for int in the above program with any other primitive data type except boolean. Be aware, however, that the comparison of floating-point numbers is very strict. A number that is the tiniest fraction different from another number is still not equal. A number that is the tiniest bit above zero is still nonzero. As a result, all the parts of a logical expression might not be evaluated. It also prints information to show you that it's being called. However, the second test produced a false result. Since this means that the whole expression must be false, why continue evaluating the rest of the expression? It could be expensive. The reason for short-circuiting, in fact, is precisely that; you can get a potential performance increase if all the parts of a logical expression do not need to be evaluated. Bitwise operators The bitwise operators allow you to manipulate individual bits in an integral primitive data type. Bitwise operators perform boolean algebra on the corresponding bits in the two arguments to produce the result. The bitwise operators come from C's low-level orientation; you were often manipulating hardware directly and had to set the bits in hardware registers. Java was originally designed to be embedded in TV set-top boxes, so this low-level orientation still made sense. However, you probably won't use the bitwise operators much. The bitwise AND operator (and) produces a one in the output bit if both input bits are one; otherwise it produces a zero. The bitwise OR operator (|) produces a one in the output bit if either input bit is a one and produces a zero only if both input bits are zero. The bitwise, EXCLUSIVE OR, or XOR (^), produces a one in the output bit if one or the other input bit is a one, but not both. The bitwise NOT (~, also called the ones complement operator) is a unary operator; it takes only one argument. The bitwise operators and logical operators use the same characters, so it is helpful to have a mnemonic device to help you remember the meanings: since bits are small, there is only one character in the bitwise operators. Bitwise operators can be combined with the = sign to unite the operation and assignment: and=, |= and ^= are all legitimate. You can perform a bitwise AND, OR and XOR, but you can't perform a bitwise NOT (presumably to prevent confusion with the logical NOT). For booleans the bitwise operators have the same effect as the logical operators except that they do not short circuit. Also, the bitwise operators on booleans gives you a XOR logical operator that is not included under the list of logical operators. You're prevented from using booleans in shift expressions, which is described next. Shift operators The shift operators also manipulate bits. They can be used solely with primitive, integral types. This operator does not exist in C or C++. If you shift a char, byte, or short, it will be promoted to int before the shift takes place, and the result will be an int. Only the five low-order bits of the right-hand side will be used. This prevents you from shifting more than the number of bits in an int. If you're operating on a long, long will be the result. Only the six low-order bits of the right-hand side will be used so you can't shift more than the number of bits in a long. There is a problem, however, with the unsigned right shift. If you use it with byte or short you might not get the correct results. The lvalue is replaced by the lvalue shifted by the rvalue. You can ignore the implementation of these for now. You'll note the use of System.out.print( ) instead of System.out.println( ). The print( ) method does not put out a new line, so it allows you to output a line in pieces. As well as demonstrating the effect of all the bitwise operators for int and long, this example also shows the minimum, maximum, +1 and -1 values for int and long so you can see what they look like. Note that the high bit represents the sign: 0 means positive and 1 means negative. Ternary if-else operator This operator is unusual because it has three operands. It is truly an operator because it produces a value, unlike the ordinary if-else statement that you'll see in the next section of this chapter. The expression is of the form boolean-exp? If boolean-exp is false, value1 is evaluated and its result becomes the value produced by the operator. Of course, you could use an ordinary if-else statement (described later), but the ternary operator is much terser. Although C prides itself on being a terse language, and the ternary operator might have been introduced partly for efficiency, you should be somewhat wary of using it on an everyday basis - it's easy to produce unreadable code. The conditional operator can be used for its side effects or for the value it produces, but in general you want the value since that's what makes the operator distinct from the if-else. So be sure to ponder your reasons when choosing the ternary operator. The comma operator The comma is used in C and C++ not only as a separator in function argument lists, but also as an operator for sequential evaluation. The sole place that the comma operator is used in Java is in for loops, which will be described later in this chapter. String operator + There's one special usage of an operator in Java: the + operator can be used to concatenate strings, as you've already seen. It seems a natural use of the + even though it doesn't fit with the traditional way that + is used. This capability seemed like a good idea in C++, so operator overloading was added to C++ to allow the C++ programmer to add meanings to almost any operator. Unfortunately, operator overloading combined with some of the other restrictions in C++ turns out to be a fairly complicated feature for programmers to design into their classes. Although operator overloading would have been much simpler to implement in Java than it was in C++, this feature was still considered too complex, so Java programmers cannot implement their own overloaded operators as C++ programmers can. The use of the String + has some interesting behavior. However, if you say: System.out.println(x + sString); earlier versions of Java will signal an error. Common pitfalls when using operators One of the pitfalls when using operators is trying to get away without parentheses when you are even the least bit uncertain about how an expression will evaluate. This is still true in Java. In C and C++ the result of this assignment will always be true if y is nonzero, and you'll probably get an infinite loop. In Java, the result of this expression is not a boolean, and the compiler expects a boolean and won't convert from an int, so it will conveniently give you a compile-time error and catch the problem before you ever try to run the program. So the pitfall never happens in Java. Bitwise AND and OR use one of the characters (and or |) while logical AND and OR use two (andand and ||). Just as with = and ==, it's easy to type just one character instead of two. In Java, the compiler again prevents this because it won't let you cavalierly use one type where it doesn't belong. Casting operators The word cast is used in the sense of casting into a mold. Java will automatically change one type of data into another when appropriate. For instance, if you assign an integral value to a floating-point variable, the compiler will automatically convert the int to a float. Casting allows you to make this type conversion explicit, or to force it when it wouldn't normally happen. To perform a cast, put the desired data type (including all modifiers) inside parentheses to the left of any value. In both casts shown here, however, the cast is superfluous, since the compiler will automatically promote an int value to a long when necessary. You can still put a cast in to make a point or to make your code more clear. In other situations, a cast is essential just to get the code to compile. In C and C++, casting can cause some headaches. Java allows you to cast any primitive type to any other primitive type, except for boolean, which doesn't allow any casting at all. Class types do not allow casting. To convert one to the other there must be special methods. Sometimes, however, the type is ambiguous. When this happens you must guide the compiler by adding some extra information in the form of characters associated with the literal value. If you try to initialize a variable with a value bigger than it can hold (regardless of the numerical form of the value), the compiler will give you an error message. Notice in the above code the maximum possible hexadecimal values for char, byte, and short. If you exceed these, the compiler will automatically make the value an int and tell you that you need a narrowing cast for the assignment. You'll know you've stepped over the line. Octal (base 8) is denoted by a leading zero in the number and digits from 0-7. There is no literal representation for binary numbers in C, C++ or Java. A trailing character after a literal value establishes its type. Upper or lowercase L means long, upper or lowercase F means float and upper or lowercase D means double. Exponents use a notation that I've always found rather dismaying: 1.39 e-47f. In science and engineering, 'e' refers to the base of natural logarithms, approximately 2.718. So if you're used to thinking in terms of e as the base of natural logarithms, you must do a mental translation when you see an expression such as 1.39 e-47f in Java; it means 1.39 x 10-47. Note that you don't need to use the trailing character when the compiler can figure out the appropriate type. With long n3 = 200; there's no ambiguity, so an L after the 200 would be superfluous. So if you want to assign back into the smaller type, you must use a cast. Java has no sizeof In C and C++, the sizeof( ) operator satisfies a specific need: it tells you the number of bytes allocated for data items. The most compelling need for sizeof( ) in C and C++ is portability. Different data types might be different sizes on different machines, so the programmer must find out how big those types are when performing operations that are sensitive to size. For example, one computer might store integers in 32 bits, whereas another might store integers as 16 bits. Programs could store larger values in integers on the first machine. As you might imagine, portability is a huge headache for C and C++ programmers. Java does not need a sizeof( ) operator for this purpose because all the data types are the same size on all machines. You do not need to think about portability on this level - it is designed into the language. X : Y A Lot Assignment = (and compound assignment like *=) Of course, with the shift and bitwise operators distributed around the table it is not a perfect mnemonic, but for non-bit operations it works. A compendium of operators The following example shows which primitive data types can be used with particular operators. Basically, it is the same example repeated over and over, but using different primitive data types. You can assign to it the values true and false, and you can test it for truth or falsehood, but you cannot add booleans or perform any other type of operation on them. In char, byte, and short you can see the effect of promotion with the arithmetic operators. Each arithmetic operation on any of those types results in an int result, which must be explicitly cast back to the original type (a narrowing conversion that might lose information) to assign back to that type. With int values, however, you do not need to cast, because everything is already an int. Don't be lulled into thinking everything is safe, though. If you multiply two ints that are big enough, you'll overflow the result. Java lets you overflow. Java is good, but it's not that good. Compound assignments do not require casts for char, byte, or short, even though they are performing promotions that have the same results as the direct arithmetic operations. On the other hand, the lack of the cast certainly simplifies the code. You can see that, with the exception of boolean, any primitive type can be cast to any other primitive type. Again, you must be aware of the effect of a narrowing conversion when casting to a smaller type, otherwise you might unknowingly lose information during the cast. Execution control Java uses all of C's execution control statements, so if you've programmed with C or C++ then most of what you see will be familiar. Most procedural programming languages have some kind of control statements, and there is often overlap among languages. In Java, the keywords include if-else, while, do-while, for, and a selection statement called switch. Java does not, however, support the much-maligned goto (which can still be the most expedient way to solve certain types of problems). You can still do a goto-like jump, but it is much more constrained than a typical goto. An example of a conditional expression is A == B. This uses the conditional operator == to see if the value of A is equivalent to the value of B. The expression returns true or false. Any of the relational operators you've seen earlier in this chapter can be used to produce a conditional statement. Note that Java doesn't allow you to use a number as a boolean, even though it's allowed in C and C++ (where truth is nonzero and falsehood is zero). If you want to use a non-boolean in a boolean test, such as if(a), you must first convert it to a boolean value using a conditional expression, such as if(a!= 0). The else is optional, so you can use if in two forms: if(Boolean-expression) statement or if(Boolean-expression) statement else statement The conditional must produce a Boolean result. The statement means either a simple statement terminated by a semicolon or a compound statement, which is a group of simple statements enclosed in braces. Anytime the word statement is used, it always implies that the statement can be simple or compound. Iteration while, do-while and for control looping and are sometimes classified as iteration statements. A statement repeats until the controlling Boolean-expression evaluates to false. The form for a while loop is while(Boolean-expression) statement The Boolean-expression is evaluated once at the beginning of the loop and again before each further iteration of the statement. In a while, if the conditional is false the first time the statement never executes. In practice, do-while is less common than while. The expression is tested before each iteration, and as soon as it evaluates to false execution will continue at the line following the for statement. At the end of each loop, the step executes. The scope of c is the expression controlled by the for. Traditional procedural languages like C require that all variables be defined at the beginning of a block so when the compiler creates a block it can allocate space for those variables. In Java and C++ you can spread your variable declarations throughout the block, defining them at the point that you need them. This allows a more natural coding style and makes code easier to understand. The ability to define variables in the control expression is limited to the for loop. You cannot use this approach with any of the other selection or iteration statements. The comma operator Earlier in this chapter I stated that the comma operator (not the comma separator, which is used to separate function arguments) has only one use in Java: in the control expression of a for loop. In both the initialization and step portions of the control expression you can have a number of statements separated by commas, and those statements will be evaluated sequentially. The previous bit of code uses this ability. Also, the initialization portion can have any number of definitions of one type. Normally, you'd use a break like this only if you didn't know when the terminating condition was going to occur. The continue statement causes execution to go back to the top of the iteration loop (thus incrementing i) whenever i is not evenly divisible by 9. When it is, the value is printed. The second portion shows an infinite loop that would, in theory, continue forever. However, inside the loop there is a break statement that will break out of the loop. In addition, you'll see that the continue moves back to the top of the loop without completing the remainder. A second form of the infinite loop is for(;;). The compiler treats both while(true) and for(;;) in the same way so whichever one you use is a matter of programming taste. The infamous goto The goto keyword has been present in programming languages from the beginning. However, goto jumps at the source-code level, and that's what brought it into disrepute. If a program will always jump from one point to another, isn't there some way to reorganize the code so the flow of control is not so jumpy? As is typical in situations like this, the middle ground is the most fruitful. The problem is not the use of goto but the overuse of goto, and in rare situations goto is the best way to structure control flow. Although goto is a reserved word in Java, it is not used in the language; Java has no goto. However, it does have something that looks a bit like a jump tied in with the break and continue keywords. It's not a jump but rather a way to break out of an iteration statement. The reason it's often thrown in with discussions of goto is because it uses the same mechanism: a label. A label is an identifier followed by a colon, like this: label1: The only place a label is useful in Java is right before an iteration statement. And that means right before - it does no good to put any other statement between the label and the iteration. And the sole reason to put a label before an iteration is if you're going to nest another iteration or a switch inside it. In case 2, the continue moves back to the beginning of the inner iteration. But in case 3, the continue label1 breaks out of the inner iteration and the outer iteration, all the way back to label1. Then it does in fact continue the iteration, but starting at the outer iteration. In case 4, the break label1 also breaks all the way out to label1, but it does not re-enter the iteration. It actually does break out of both iterations. Note that break breaks out of the for loop, and that the increment-expression doesn't occur until the end of the pass through the for loop. Since break skips the increment expression, the increment is performed directly in the case of i == 3. The continue outer statement in the case of I == 7 also goes to the top of the loop and also skips the increment, so it too is incremented directly. 2. A labeled continue goes to the label and re-enters the loop right after that label. 3. A break drops out of the bottom of the loop. 4. A labeled break drops out of the bottom of the end of the loop denoted by the label. In Dijkstra's goto considered harmful paper, what he specifically objected to was the labels, not the goto. He observed that the number of bugs seems to increase with the number of labels in a program. Labels and gotos make programs difficult to analyze statically, since it introduces cycles in the program execution graph. Note that Java labels don't suffer from this problem, since they are constrained in their placement and can't be used to transfer control in an ad hoc manner. It's also interesting to note that this is a case where a language feature is made more useful by restricting the power of the statement. The switch statement selects from among pieces of code based on the value of an integral expression. The switch compares the result of integral-selector to each integral-value. If it finds a match, the corresponding statement (simple or compound) executes. If no match occurs, the default statement executes. You will notice in the above definition that each case ends with a break, which causes execution to jump to the end of the switch body. This is the conventional way to build a switch statement, but the break is optional. If it is missing, the code for the following case statements execute until a break is encountered. Although you don't usually want this kind of behavior, it can be useful to an experienced programmer. Note the last statement, for the default, doesn't have a break because the execution just falls through to where the break would have taken it anyway. You could put a break at the end of the default statement with no harm if you considered it important for style's sake. The switch statement is a clean way to implement multi-way selection (i.e., selecting from among a number of different execution paths), but it requires a selector that evaluates to an integral value such as int or char. If you want to use, for example, a string or a floating-point number as a selector, it won't work in a switch statement. For non-integral types, you must use a series of if statements. Although it appears you're switching on a character here, the switch statement is actually using the integral value of the character. The singly-quoted characters in the case statements also produce integral values that are used for comparison. Notice how the cases can be stacked on top of each other to provide multiple matches for a particular piece of code. You should also be aware that it's essential to put the break statement at the end of a particular case, otherwise control will simply drop through and continue processing on the next case. Calculation details The statement: char c = (char)(Math.random() * 26 + 'a'); deserves a closer look. Math.random( ) produces a double, so the value 26 is converted to a double to perform the multiplication, which also produces a double. This means that 'a' must be converted to a double to perform the addition. The double result is turned back into a char with a cast. First, what does the cast to char do? That is, if you have the value 29.7 and you cast it to a char, is the resulting value 30 or 29? The second question has to do with Math.random( ). Does it produce a value from zero to one, inclusive or exclusive of the value '1'? If you consider that there are 2128 different double fractions between 0 and 1, the likelihood of reaching any one value experimentally might exceed the lifetime of one computer, or even one experimenter. It turns out that 0.0 is included in the output of Math.random( ). Summary This chapter concludes the study of fundamental features that appear in most programming languages: calculation, operator precedence, type casting, and selection and iteration. Now you're ready to begin taking steps that move you closer to the world of object-oriented programming. The next chapter will cover the important issues of initialization and cleanup of objects, followed in the subsequent chapter by the essential concept of implementation hiding. Exercises 1. Write a program that prints values from one to 100. 2. Modify Exercise 1 so that the program exits by using the break keyword at value 47. Try using return instead. 3. Create a switch statement that prints a message for each case, and put the switch inside a for loop that tries each case. Put a break after each case and test it, then remove the breaks and see what happens. Two of these safety issues are initialization and cleanup. Many C bugs occur when the programmer forgets to initialize a variable. This is especially true with libraries when users don't know how to initialize a library component, or even that they must. Cleanup is a special problem because it's easy to forget about an element when you're done with it, since it no longer concerns you. Thus, the resources used by that element are retained and you can easily end up running out of resources (most notably memory). C++ introduced the concept of a constructor, a special method automatically called when an object is created. Java also adopted the constructor, and in addition has a garbage collector that automatically releases memory resources when they're no longer being used. This chapter examines the issues of initialization and cleanup and their support in Java. Guaranteed initialization with the constructor You can imagine creating a method called initialize( ) for every class you write. The name is a hint that it should be called before using the object. Unfortunately, this means the user must remember to call the method. In Java, the class designer can guarantee initialization of every object by providing a special method called a constructor. If a class has a constructor, Java automatically calls that constructor when an object is created, before users can even get their hands on it. So initialization is guaranteed. The next challenge is what to name this method. There are two issues. The first is that any name you use could clash with a name you might like to use as a member in the class. The second is that because the compiler is responsible for calling the constructor, it must always know which method to call. The C++ solution seems the easiest and most logical, so it's also used in Java: The name of the constructor is the same as the name of the class. It makes sense that such a method will be called automatically on initialization. It is guaranteed that the object will be properly initialized before you can get your hands on it. Note that the coding style of making the first letter of all methods lower case does not apply to constructors, since the name of the constructor must match the name of the class exactly. Like any method, the constructor can have arguments to allow you to specify how an object is created. Constructors eliminate a large class of problems and make the code easier to read. In the preceding code fragment, for example, you don't see an explicit call to some initialize( ) method that is conceptually separate from definition. In Java, definition and initialization are unified concepts - you can't have one without the other. The constructor is an unusual type of method because it has no return value. This is distinctly different from a void return value, in which the method returns nothing but you still have the option to make it return something else. Constructors return nothing and you don't have an option. If there were a return value, and if you could select your own, the compiler would somehow need to know what to do with that return value. Method overloading One of the important features in any programming language is the use of names. When you create an object, you give a name to a region of storage. A method is a name for an action. By using names to describe your system, you create a program that is easier for people to understand and change. It's a lot like writing prose - the goal is to communicate with your readers. You refer to all objects and methods by using names. Well-chosen names make it easier for you and others to understand your code. A problem arises when mapping the concept of nuance in human language onto a programming language. Often, the same word expresses a number of different meanings - it's overloaded. This is useful, especially when it comes to trivial differences. You say wash the shirt, wash the car, and wash the dog. It would be silly to be forced to say, shirtWash the shirt, carWash the car, and dogWash the dog just so the listener doesn't need to make any distinction about the action performed. Most human languages are redundant, so even if you miss a few words, you can still determine the meaning. We don't need unique identifiers - we can deduce meaning from context. Most programming languages (C in particular) require you to have a unique identifier for each function. So you could not have one function called print( ) for printing integers and another called print( ) for printing floats - each function requires a unique name. In Java, another factor forces the overloading of method names: the constructor. Because the constructor's name is predetermined by the name of the class, there can be only one constructor name. But what if you want to create an object in more than one way? For example, suppose you build a class that can initialize itself in a standard way and by reading information from a file. You need two constructors, one that takes no arguments (the default constructor), and one that takes a String as an argument, which is the name of the file from which to initialize the object. Both are constructors, so they must have the same name - the name of the class. Thus method overloading is essential to allow the same method name to be used with different argument types. And although method overloading is a must for constructors, it's a general convenience and can be used with any method. To support this, there are two constructors, one that takes no arguments (we call constructors that take no arguments default constructors1) and one that takes the existing height. You might also want to call the info( ) method in more than one way. For example, with a String argument if you have an extra message you want printed, and without if you have nothing more to say. It would seem strange to give two separate names to what is obviously the same concept. Fortunately, method overloading allows you to use the same name for both. Distinguishing overloaded methods If the methods have the same name, how can Java know which method you mean? There's a simple rule: Each overloaded method must take a unique list of argument types. If you think about this for a second, it makes sense: how else could a programmer tell the difference between two methods that have the same name, other than by the types of their arguments? Overloading with primitives Primitives can be automatically promoted from a smaller type to a larger one and this can be slightly confusing in combination with overloading. In all other cases, if you have a data type that is smaller than the argument in the method, that data type is promoted. What happens if your argument is bigger than the argument expected by the overloaded method? If your argument is wider then you must cast to the necessary type using the type name in parentheses. If you don't do this, the compiler will issue an error message. You should be aware that this is a narrowing conversion, which means you might lose information during the cast. This is why the compiler forces you to do it - to flag the narrowing conversion. Overloading on return values It is common to wonder Why only class names and method argument lists? However, you can call a method and ignore the return value; this is often referred to as calling a method for its side effect since you don't care about the return value but instead want the other effects of the method call. So if you call the method this way: f(); how can Java determine which f( ) should be called? And how could someone reading the code see it? Because of this sort of problem, you cannot use return value types to distinguish overloaded methods. Without it we would have no method to call to build our object. To allow you to write the code in a convenient object-oriented syntax in which you send a message to an object, the compiler does some undercover work for you. There's a secret first argument passed to the method f( ), and that argument is the handle to the object that's being manipulated. So the two method calls above become something like: Banana.f(a,1); Banana.f(b,2); This is internal and you can't write these expressions and get the compiler to accept them, but it gives you an idea of what's happening. Suppose you're inside a method and you'd like to get the handle to the current object. Since that handle is passed secretly by the compiler, there's no identifier for it. However, for this purpose there's a keyword: this. The this keyword - which can be used only inside a method - produces the handle to the object the method has been called for. You can treat this handle just like any other object handle. Keep in mind that if you're calling a method of your class from within another method of your class, you don't need to use this; you simply call the method. The current this handle is automatically used for the other method. The compiler does it for you automatically. The this keyword is used only for those special cases in which you need to explicitly use the handle to the current object. Calling constructors from constructors When you write several constructors for a class, there are times when you'd like to call one constructor from another to avoid duplicating code. You can do this using the this keyword. Normally, when you say this, it is in the sense of this object or the current object, and by itself it produces the handle to the current object. In a constructor, the this keyword takes on a different meaning when you give it an argument list: it makes an explicit call to the constructor that matches that argument list. In addition, the constructor call must be the first thing you do or you'll get a compiler error message. This example also shows another way you'll see this used. Since the name of the argument s and the name of the member data s are the same, there's an ambiguity. You can resolve it by saying this.s to refer to the member data. You'll often see this form used in Java code, and it's used in numerous places in this book. In print( ) you can see that the compiler won't let you call a constructor from inside any method other than a constructor. The meaning of static With the this keyword in mind, you can more fully understand what it means to make a method static. It means that there is no this for that particular method. You cannot call non-static methods from inside static methods2 (although the reverse is possible), and you can call a static method for the class itself, without any object. In fact, that's primarily what a static method is for. It's as if you're creating the equivalent of a global function (from C). Except global functions are not permitted in Java, and putting the static method inside a class allows it access to other static methods and to static fields. Some people argue that static methods are not object-oriented since they do have the semantics of a global function; with a static method you don't send a message to an object, since there's no this. This is probably a fair argument, and if you find yourself using a lot of static methods you should probably rethink your strategy. However, statics are pragmatic and there are times when you genuinely need them, so whether or not they are proper OOP should be left to the theoreticians. Indeed, even Smalltalk has the equivalent in its class methods. Cleanup: finalization and garbage collection Programmers know about the importance of initialization, but often forget the importance of cleanup. After all, who needs to clean up an int? But with libraries, simply letting go of an object once you're done with it is not always safe. Of course, Java has the garbage collector to reclaim the memory of objects that are no longer used. Now consider a very special and unusual case. Suppose your object allocates special memory without using new. The garbage collector knows only how to release memory allocated with new, so it won't know how to release the object's special memory. To handle this case, Java provides a method called finalize( ) that you can define for your class. Here's how it's supposed to work. When the garbage collector is ready to release the storage used for your object, it will first call finalize( ), and only on the next garbage-collection pass will it reclaim the object's memory. So if you choose to use finalize( ), it gives you the ability to perform some important cleanup at the time of garbage collection. This is a potential programming pitfall because some programmers, especially C++ programmers, might initially mistake finalize( ) for the destructor in C++, which is a function that is always called when an object is destroyed. But it is important to distinguish between C++ and Java here, because in C++ objects always get destroyed (in a bug-free program), whereas in Java objects do not always get garbage-collected. Or, put another way: Garbage collection is not destruction. If you remember this, you will stay out of trouble. What it means is that if there is some activity that must be performed before you no longer need an object, you must perform that activity yourself. Java has no destructor or similar concept, so you must create an ordinary method to perform this cleanup. For example, suppose in the process of creating your object it draws itself on the screen. If you don't explicitly erase its image from the screen, it might never get cleaned up. If you put some kind of erasing functionality inside finalize( ), then if an object is garbage-collected, the image will first be removed from the screen, but if it isn't, the image will remain. So a second point to remember is: Your objects might not get garbage collected. You might find that the storage for an object never gets released because your program never nears the point of running out of storage. If your program completes and the garbage collector never gets around to releasing the storage for any of your objects, that storage will be returned to the operating system en masse as the program exits. This is a good thing, because garbage collection has some overhead, and if you never do it you never incur that expense. What is finalize( ) for? You might believe at this point that you should not use finalize( ) as a general-purpose cleanup method. What good is it? A third point to remember is: Garbage collection is only about memory. That is, the sole reason for the existence of the garbage collector is to recover memory that your program is no longer using. So any activity that is associated with garbage collection, most notably your finalize( ) method, must also be only about memory and its deallocation. Does this mean that if your object contains other objects finalize( ) should explicitly release those objects? Well, no - the garbage collector takes care of the release of all object memory regardless of how the object is created. It turns out that the need for finalize( ) is limited to special cases, in which your object can allocate some storage in some way other than creating an object. But, you might observe, everything in Java is an object so how can this be? It would seem that finalize( ) is in place because of the possibility that you'll do something C-like by allocating memory using a mechanism other than the normal one in Java. This can happen primarily through native methods, which are a way to call non-Java code from Java. Inside the non-Java code, C's malloc( ) family of functions might be called to allocate storage, and unless you call free( ) that storage will not be released, causing a memory leak. Of course, free( ) is a C and C++ function, so you'd need call it in a native method inside your finalize( ). After reading this, you probably get the idea that you won't use finalize( ) much. You're correct; it is not the appropriate place for normal cleanup to occur. So where should normal cleanup be performed? You must perform cleanup To clean up an object, the user of that object must call a cleanup method at the point the cleanup is desired. This sounds pretty straightforward, but it collides a bit with the C++ concept of the destructor. In C++, all objects are destroyed. Or rather, all objects should be destroyed. If the C++ object is created as a local, i.e. on the stack (not possible in Java), then the destruction happens at the closing curly brace of the scope in which the object was created. If the object was created using new (like in Java) the destructor is called when the programmer calls the C++ operator delete (which doesn't exist in Java). If the programmer forgets, the destructor is never called and you have a memory leak, plus the other parts of the object never get cleaned up. In contrast, Java doesn't allow you to create local objects - you must always use new. But in Java, there's no delete to call for releasing the object since the garbage collector releases the storage for you. So from a simplistic standpoint you could say that because of garbage collection, Java has no destructor. You'll see as this book progresses, however, that the presence of a garbage collector does not remove the need or utility of destructors. One of the things finalize( ) can be useful for is observing the process of garbage collection. Since the garbage collector can run at any time, you don't know exactly when it will start up, so there's a flag called gcrun to indicate whether the garbage collector has started running yet. A second flag f is a way for Chair to tell the main( ) loop that it should stop making objects. Both of these flags are set within finalize( ), which is called during garbage collection. Two other static variables, created and finalized, keep track of the number of objs created versus the number that get finalized by the garbage collector. Finally, each Chair has its own (non-static) int i so it can keep track of what number it is. When Chair number 47 is finalized, the flag is set to true to bring the process of Chair creation to a stop. The creation of a String object during each iteration is simply extra garbage being created to encourage the garbage collector to kick in, which it will do when it starts to get nervous about the amount of memory available. These methods were available in Java 1.0, but the runFinalizersOnExit( ) method that is invoked by using the after argument is available only in Java 1.13 and beyond. The preceding program shows that, in Java 1.1, the promise that finalizers will always be run holds true, but only if you explicitly force it to happen yourself. Thus, not all finalizers get called by the time the program completes.4 To force finalization to happen, you can call System.gc( ) followed by System.runFinalization( ). This will destroy all the objects that are no longer in use up to that point. The odd thing about this is that you call gc( ) before you call runFinalization( ), which seems to contradict the Sun documentation, which claims that finalizers are run first, and then the storage is released. However, if you call runFinalization( ) first, and then gc( ), the finalizers will not be executed. One reason that Java 1.1 might default to skipping finalization for all objects is because it seems to be expensive. When you use either of the approaches that force garbage collection you might notice longer delays than you would without the extra finalization. Member initialization Java goes out of its way to guarantee that any variable is properly initialized before it is used. In the case of variables that are defined locally to a method, this guarantee comes in the form of a compile-time error. Of course, the compiler could have given i a default value, but it's more likely that this is a programmer error and a default value would have covered that up. Forcing the programmer to provide an initialization value is more likely to catch a bug. If a primitive is a data member of a class, however, things are a bit different. Since any method can initialize or use that data, it might not be practical to force the user to initialize it to its appropriate value before the data is used. However, it's unsafe to leave it with a garbage value, so each primitive data member of a class is guaranteed to get an initial value. You'll see later that when you define an object handle inside a class without initializing it to a new object, that handle is given a value of null. You can see that even though the values are not specified, they automatically get initialized. So at least there's no threat of working with uninitialized variables. Specifying initialization What happens if you want to give a variable an initial value? One direct way to do this is simply to assign the value at the point you define the variable in the class. This approach to initialization is simple and straightforward. It has the limitation that every object of type Measurement will get these same initialization values. Sometimes this is exactly what you need, but at other times you need more flexibility. Constructor initialization The constructor can be used to perform initialization, and this gives you greater flexibility in your programming since you can call methods and perform actions at run time to determine the initial values. There's one thing to keep in mind, however: you aren't precluding the automatic initialization, which happens before the constructor is entered. This is true with all the primitive types and with object handles, including those that are given explicit initialization at the point of definition. Even if the variable definitions are scattered throughout in between method definitions, the variables are initialized before any methods can be called - even the constructor. In addition, t3 is re-initialized inside the constructor. The output is: Tag(1) Tag(2) Tag(3) Card() Tag(33) f() Thus, the t3 handle gets initialized twice, once before and once during the constructor call. Static data initialization When the data is static the same thing happens; if it's a primitive and you don't initialize it, it gets the standard primitive initial values. If it's a handle to an object, it's null unless you create a new object and attach your handle to it. If you want to place initialization at the point of definition, it looks the same as for non-statics. But since there's only a single piece of storage for a static, regardless of how many objects are created the question of when that storage gets initialized arises. Note that Cupboard creates a non-static Bowl b3 prior to the static definitions. If you don't create a Table object and you never refer to Table.b1 or Table.b2, the static Bowl b1 and b2 will never be created. However, they are created only when the first Table object is created (or the first static access occurs). After that, the static object is not re-initialized. The order of initialization is statics first, if they haven't already been initialized by a previous object creation, and then the non-static objects. You can see the evidence of this in the output. It's helpful to summarize the process of creating an object. 2. As Dog.class is loaded (which creates a Class object, which you'll learn about later), all of its static initializers are run. Thus, static initialization takes place only once, as the Class object is loaded for the first time. 3. When you create a new Dog( ), the construction process for a Dog object first allocates enough storage for a Dog object on the heap. 4. This storage is wiped to zero, automatically setting all the primitives in Dog to their default values (zero for numbers and the equivalent for boolean and char). 5. Any initializations that occur at the point of field definition are executed. 6. Constructors are executed. As you shall see in Chapter 6, this might actually involve a fair amount of activity, especially when inheritance is involved. Explicit static initialization Java allows you to group other static initializations inside a special static construction clause (sometimes called a static block) in a class. This code, like the other static initialization, is executed only once, the first time you make an object of that class or you access a static member of that class (even if you never make an object of that class). If both (1) and (2) are commented out, the static initialization for Cups never occurs. Non-static instance initialization Java 1.1 provides a similar syntax for initializing non-static variables for each object. This syntax is necessary to support the initialization of anonymous inner classes (see Chapter 7). Array initialization Initializing arrays in C is error-prone and tedious. C++ uses aggregate initialization to make it much safer.6 Java has no aggregates like C++, since everything is an object in Java. It does have arrays, and these are supported with array initialization. An array is simply a sequence of either objects or primitives, all the same type and packaged together under one identifier name. The former style, however, is probably a more sensible syntax, since it says that the type is an int array. That style will be used in this book. The compiler doesn't allow you to tell it how big the array is. This brings us back to that issue of handles. All that you have at this point is a handle to an array, and there's been no space allocated for the array. To create storage for the array you must write an initialization expression. For arrays, initialization can appear anywhere in your code, but you can also use a special kind of initialization expression that must occur at the point where the array is created. This special initialization is a set of values surrounded by curly braces. The storage allocation (the equivalent of using new) is taken care of by the compiler in this case. There's something new here: all arrays have an intrinsic member (whether they're arrays of objects or arrays of primitives) that you can query - but not change - to tell you how many elements there are in the array. This member is length. Since arrays in Java, like C and C++, start counting from element zero, the largest element you can index is length - 1. If you go out of bounds, C and C++ quietly accept this and allow you to stomp all over your memory, which is the source of many infamous bugs. However, Java protects you against such problems by causing a run-time error (an exception, the subject of Chapter 9) if you step out of bounds. Of course, checking every array access costs time and code and there's no way to turn it off, which means that array accesses might be a source of inefficiency in your program if they occur at a critical juncture. For Internet security and programmer productivity, the Java designers thought that this was a worthwhile tradeoff. What if you don't know how many elements you're going to need in your array while you're writing the program? You simply use new to create the elements in the array. In addition, you'll see from the output of this program that array elements of primitive types are automatically initialized to empty values. Here, the handle issue comes up again because what you create is an array of handles. Take a look at the formation of the String object inside the print statements. You can see that the handle to the Integer object is automatically converted to produce a String representing the value inside the object. It's also possible to initialize arrays of objects using the curly-brace-enclosed list. The final comma in the list of initializers is optional. These included, if you choose, unknown quantity of arguments as well as unknown type. In Chapter 11 (run-time type identification or RTTI) you'll learn how to discover the exact type of such objects so that you can do something more interesting with them. The first example shows a multidimensional array of primitives. The second example shows a three-dimensional array allocated with new. The second new inside the for loop fills out the elements but leaves the third index undetermined until you hit the third new. You will see from the output that array values are automatically initialized to zero if you don't give them an explicit initialization value. Summary The seemingly elaborate mechanism for initialization, the constructor, should give you a strong hint about the critical importance placed on initialization in the language. As Stroustrup was designing C++, one of the first observations he made about productivity in C was that improper initialization of variables causes a significant portion of programming problems. These kinds of bugs are hard to find, and similar issues apply to improper cleanup. Because constructors allow you to guarantee proper initialization and cleanup (the compiler will not allow an object to be created without the proper constructor calls), you get complete control and safety. In C++, destruction is quite important because objects created with new must be explicitly destroyed. In Java, the garbage collector automatically releases the memory for all objects, so the equivalent cleanup method in Java isn't necessary much of the time. In cases where you don't need destructor-like behavior, Java's garbage collector greatly simplifies programming, and adds much-needed safety in managing memory. Some garbage collectors are even cleaning up other resources like graphics and file handles. However, the garbage collector does add a run-time cost, the expense of which is difficult to put into perspective because of the overall slowness of Java interpreters at this writing. As this changes, we'll be able to discover if the overhead of the garbage collector will preclude the use of Java for certain types of programs. In particular, when you create new classes using either composition or inheritance the guarantee of construction also holds, and some additional syntax is necessary to support this. You'll learn about composition, inheritance and how they affect constructors in future chapters. Exercises 1. Create a class with a default constructor (one that takes no arguments) that prints a message. Create an object of this class. 2. Add an overloaded constructor to Exercise 1 that takes a String argument and prints it along with your message. 3. Create an array of object handles of the class you created in Exercise 2, but don't actually create objects to assign into the array. When you run the program, notice whether the initialization messages from the constructor calls are printed. 4. Complete Exercise 3 by creating objects to attach to the array of handles. 5. Experiment with Garbage.java by running the program using the arguments before, after and none. Repeat the process and see if you detect any patterns in the output. Change the code so that System.runFinalization( ) is called before System.gc( ) and observe the results. The user (client programmer) of that library must be able to rely on the part they use, and know that they won't need to rewrite code if a new version of the library comes out. On the flip side, the library creator must have the freedom to make modifications and improvements with the certainty that the client programmer's code won't be affected by those changes. This can be achieved through convention. For example, the library programmer must agree to not remove existing methods when modifying a class in the library, since that would break the client programmer's code. The reverse situation is thornier, however. In the case of a data member, how can the library creator know which data members have been accessed by client programmers? This is also true with methods that are only part of the implementation of a class, and not meant to be used directly by the client programmer. But what if the library creator wants to rip out an old implementation and put in a new one? Changing any of those members might break a client programmer's code. Thus the library creator is in a strait jacket and can't change anything. To solve this problem, Java provides access specifiers to allow the library creator to say what is available to the client programmer and what is not. The levels of access control from most access to least access are public, friendly (which has no keyword), protected, and private. From the previous paragraph you might think that, as a library designer, you'll want to keep everything as private as possible, and expose only the methods that you want the client programmer to use. This is exactly right, even though it's often counterintuitive for people who program in other languages (especially C) and are used to accessing everything without restriction. By the end of this chapter you should be convinced of the value of access control in Java. The concept of a library of components and the control over who can access the components of that library is not complete, however. There's still the question of how the components are bundled together into a cohesive library unit. This is controlled with the package keyword in Java, and the access specifiers are affected by whether a class is in the same package or in a separate package. So to begin this chapter, you'll learn how library components are placed into packages. Then you'll be able to understand the complete meaning of the access specifiers. Since Vector is in java.util, you can now either specify the full name java.util.Vector (which you can do without the import statement), or you can simply say Vector (because of the import). If you want to bring in a single class, you can name that class in the import statement import java.util.Vector; Now you can use Vector with no qualification. However, none of the other classes in java.util are available. The reason for all this importing is to provide a mechanism to manage name spaces. The names of all your class members are insulated from each other. A method f( ) inside a class A will not clash with an f( ) that has the same signature (argument list) in class B. But what about the class names? Suppose you create a stack class that is installed on a machine that already has a stack class that's written by someone else? With Java on the Internet, this can happen without the user knowing it since classes can get downloaded automatically in the process of running a Java program. This potential clashing of names is why it's important to have complete control over the name spaces in Java, and to be able to create a completely unique name regardless of the constraints of the Internet. So far, most of the examples in this book have existed in a single file and have been designed for local use, and haven't bothered with package names. If you're planning to create a program that is Internet friendly, however, you must think about preventing class name clashes. When you create a source-code file for Java, it's commonly called a compilation unit (sometimes a translation unit). Each compilation unit must have a name ending in.java, and inside the compilation unit there can be a public class that must have the same name as the file (including capitalization, but excluding the .java filename extension). If you don't do this, the compiler will complain. There can be only one public class in each compilation unit (again, the compiler will complain). The rest of the classes in that compilation unit, if there are any, are hidden from the world outside that package because they're not public, and they comprise support classes for the main public class. When you compile a.java file you get an output file with exactly the same name but an extension of .class for each class in the .java file. Thus you can end up with quite a few .class files from a small number of .java files. That's not how Java works. A working program is a bunch of .class files, which can be packaged and compressed into a JAR file (using the jar utility in Java 1.1). The Java interpreter is responsible for finding, loading and interpreting these files.1 A library is also a bunch of these class files. Each file has one class that is public (you're not forced to have a public class, but it's typical), so there's one component for each file. If you want to say that all these components (that are in their own separate.java and .class files) belong together, that's where the package keyword comes in. When you say: package mypackage; at the beginning of a file, where the package statement must appear as the first non-comment in the file, you're stating that this compilation unit is part of a library named mypackage. Note that the convention for Java packages is to use all lowercase letters, even for intermediate words. For example, suppose the name of the file is MyClass.java. Creating unique package names You might observe that, since a package never really gets packaged into a single file, a package could be made up of many.class files, and things could get a bit cluttered. To prevent this, a logical thing to do is to place all the .class files for a particular package into a single directory; that is, use the hierarchical file structure of the operating system to your advantage. This is how Java handles the problem of clutter. It also solves two other problems: creating unique package names and finding those classes that might be buried in a directory structure someplace. This is accomplished, as was introduced in Chapter 2, by encoding the path of the location of the.class file into the name of the package. The compiler enforces this, but by convention, the first part of the package name is the Internet domain name of the creator of the class, reversed. Since Internet domain names are guaranteed to be unique (by InterNIC,2 who controls their assignment) if you follow this convention it's guaranteed that your package name will be unique and thus you'll never have a name clash. If you've decided to start publishing Java code it's worth the relatively small effort to get a domain name. The Java interpreter proceeds as follows. First, it finds the environment variable CLASSPATH (set via the operating system when Java, or a tool like a Java-enabled browser, is installed on a machine). CLASSPATH contains one or more directories that are used as roots for a search for.class files. This is then concatenated to the various entries in the CLASSPATH. That's where it looks for the .class file with the name corresponding to the class you're trying to create. To understand this, consider my domain name, which is bruceeckel.com. By reversing this, com.bruceeckel establishes my unique global name for my classes. That's taken care of in the CLASSPATH environment variable, which is, on my machine: CLASSPATH=.;D:\JAVA\LIB;C:\DOC\JavaT You can see that the CLASSPATH can contain a number of alternative search paths. There's a variation when using JAR files, however. You must put the name of the JAR file in the classpath, not just the path where it's located. Note that both the classes and the desired methods in Vector and List must be public. If it finds only X.class, that's what it must use. However, if it also finds an X.java in the same directory, the compiler will compare the date stamp on the two files, and if X.java is more recent than X.class, it will automatically recompile X.java to generate an up-to-date X.class. If a class is not in a.java file of the same name as that class, this behavior will not occur for that class. Collisions What happens if two libraries are imported via * and they include the same names? For example, suppose a program does this: import com.bruceeckel.util.*; import java.util.*; Since java.util.* also contains a Vector class, this causes a potential collision. However, as long as the collision doesn't actually occur, everything is OK - this is good because otherwise you might end up doing a lot of typing to prevent collisions that would never happen. The collision does occur if you now try to make a Vector: Vector v = new Vector(); Which Vector class does this refer to? The compiler can't know, and the reader can't know either. So the compiler complains and forces you to be explicit. A custom tool library With this knowledge, you can now create your own libraries of tools to reduce or eliminate duplicate code. Consider, for example, creating an alias for System.out.println( ) to reduce typing. Especially with early implementations of Java, setting the classpath correctly is generally quite a headache. During the development of this book, the P.java file was introduced and seemed to work fine, but at some point it began breaking. For a long time I was certain that this was the fault of one implementation of Java or another, but finally I discovered that at one point I had introduced a program (CodePackager.java, shown in Chapter 17) that used a different class P. Because it was used as a tool, it was sometimes placed in the classpath, and other times it wasn't. When it was, the P in CodePackager.java was found first by Java when executing a program in which it was looking for the class in com.bruceeckel.tools, and the compiler would say that a particular method couldn't be found. This was frustrating because you can see the method in the above class P and no further diagnostics were reported to give you a clue that it was finding a completely different class. This is slightly different from the case described on page 196 because there the offending classes were both in packages, and here there was a P that was not in a package, but could still be found during a normal classpath search. If you're having an experience like this, check to make sure that there's only one class of each name anywhere in your classpath. Using imports to change behavior A feature that is missing from Java is C's conditional compilation, which allows you to change a switch and get different behavior without changing any other code. The reason such a feature was left out of Java is probably because it is most often used in C to solve cross-platform issues: different portions of the code are compiled depending on the platform that the code is being compiled for. Since Java is intended to be automatically cross-platform, such a feature should not be necessary. However, there are other valuable uses for conditional compilation. A very common use is for debugging code. The debugging features are enabled during development, and disabled for a shipping product. Allen Holub (www.holub.com) came up with the idea of using packages to mimic conditional compilation. He used this to create a Java version of C's very useful assertion mechanism, whereby you can say this should be true or this should be false and if the statement doesn't agree with your assertion you'll find out about it. Such a tool is quite helpful during debugging. In Chapter 9, you'll learn about a more sophisticated tool for dealing with errors called exception handling, but the perr( ) method will work fine in the meantime. This technique can be used for any kind of conditional code. Package caveat It's worth remembering that anytime you create a package, you implicitly specify a directory structure when you give the package a name. The package must live in the directory indicated by its name, which must be a directory that is searchable starting from the CLASSPATH. If you get a message like this, try commenting out the package statement, and if it runs you'll know where the problem lies. Java access specifiers The Java access specifiers public, protected and private are placed in front of each definition for each member in your class, whether it's a data member or a method. Each access specifier controls the access for only that particular definition. This is a distinct contrast to C++, in which the access specifier controls all the definitions following it until another access specifier comes along. One way or another, everything has some kind of access specified for it. In the following sections, you'll learn all about the various types of access, starting with the default access. Friendly What if you give no access specifier at all, as in all the examples before this chapter? Since a compilation unit - a file - can belong only to a single package, all the classes within a single compilation unit are automatically friendly with each other. Thus, friendly elements are also said to have package access. Friendly access allows you to group related classes together in a package so that they can easily interact with each other. When you put classes together in a package (thus granting mutual access to their friendly members; e.g. making them friends) you own the code in that package. It makes sense that only code that you own should have friendly access to other code that you own. You could say that friendly access gives a meaning or a reason for grouping classes together in a package. In many languages the way you organize your definitions in files can be willy-nilly, but in Java you're compelled to organize them in a sensible fashion. In addition, you'll probably want to exclude classes that shouldn't have access to the classes being defined in the current package. An important question in any relationship is Who can access my private implementation? The class controls which code has access to its members. There's no magic way to break in; someone in another package can't declare a new class and say, Hi, I'm a friend of Bob's! and expect to see the protected, friendly, and private members of Bob. The only way to grant access to a member is to: 1. Make the member public. Then everybody, everywhere, can access it. 2. Make the member friendly by leaving off any access specifier, and put the other classes in the same package. Then the other classes can access the member. 3. As you'll see in a later chapter where inheritance is introduced, an inherited class can access a protected member as well as a public member (but not private members). It can access friendly members only if the two classes are in the same package. But don't worry about that now. This is the most civilized approach in terms of OOP, and it is fundamental to Java Beans, as you'll see in Chapter 13. Don't make the mistake of thinking that Java will always look at the current directory as one of the starting points for searching. If you don't have a '.' as one of the paths in your CLASSPATH, Java won't look there. You'd typically think that Pie and f( ) are friendly and therefore not available to Cake. They are friendly - that part is correct. The reason that they are available in Cake.java is because they are in the same directory and have no explicit package name. Java treats files like this as implicitly part of the default package for that directory, and therefore friendly to all the other files in that directory. The private keyword that means no one can access that member except that particular class, inside methods of that class. Other classes in the same package cannot access private members, so it's as if you're even insulating the class against yourself. On the other hand, it's not unlikely that a package might be created by several people collaborating together, so private allows you to freely change that member without concern that it will affect another class in the same package. The default friendly package access is often an adequate amount of hiding; remember, a friendly member is inaccessible to the user of the package. This is nice, since the default access is the one that you normally use. Making a method private guarantees that you retain this option. See Chapter 12 for issues about aliasing.) protected: sort of friendly The protected access specifier requires a jump ahead to understand. First, you should be aware that you don't need to understand this section to continue through the book up through the inheritance chapter. But for completeness, here is a brief description and example using protected. The protected keyword deals with a concept called inheritance, which takes an existing class and adds new members to that class without touching the existing class, which we refer to as the base class. You can also change the behavior of existing members of the class. If you create a new package and you inherit from a class in another package, the only members you have access to are the public members of the original package. That's what protected does. But since foo( ) is friendly in a foreign package, it's unavailable to us in this one. Of course, you could make it public, but then everyone would have access and maybe that's not what you want. However, it is not public. Interface and implementation Access control is often referred to as implementation hiding. The first is to establish what the client programmers can and can't use. You can build your internal mechanisms into the structure without worrying that the client programmers will think it's part of the interface that they should be using. This feeds directly into the second reason, which is to separate the interface from the implementation. We're now in the world of object-oriented programming, where a class is actually describing a class of objects, as you would describe a class of fishes or a class of birds. Any object belonging to this class will share these characteristics and behaviors. The class is a description of the way all objects of this type will look and act. In the original OOP language, Simula-67, the keyword class was used to describe a new data type. The same keyword has been used for most object-oriented languages. This is the focal point of the whole language: the creation of new data types that are more than just boxes containing data and methods. The class is the fundamental OOP concept in Java. However, with the comment documentation supported by javadoc (described in Chapter 2) the issue of code readability by the client programmer becomes less important. That is, you still see the source code - the implementation - because it's right there in the class. By the time you read this, good browsers should be an expected part of any good Java development tool. Class access In Java, the access specifiers can also be used to determine which classes within a library will be available to the users of that library. If you want a class to be available to a client programmer, you place the public keyword somewhere before the opening brace of the class body. This controls whether the client programmer can even create an object of the class. To control the access of a class, the specifier must appear before the keyword class. The idea is that each compilation unit has a single public interface represented by that public class. It can have as many supporting friendly classes as you want. If you have more than one public class inside a compilation unit, the compiler will give you an error message. 2. The name of the public class must exactly match the name of the file containing the compilation unit, including capitalization. So for Widget, the name of the file must be Widget.java, not widget.java or WIDGET.java. Again, you'll get a compile-time error if they don't agree. 3. It is possible, though not typical, to have a compilation unit with no public class at all. In this case, you can name the file whatever you like. What if you've got a class inside mylib that you're just using to accomplish the tasks performed by Widget or some other public class in mylib? You don't want to go to the bother of creating documentation for the client programmer, and you think that sometime later you might want to completely change things and rip out your class altogether, substituting a different one. To give you this flexibility, you need to ensure that no client programmers become dependent on your particular implementation details hidden inside mylib. To accomplish this, you just leave the public keyword off the class, in which case it becomes friendly. The word before the method name (access) tells what the method returns. So far this has most often been void, which means it returns nothing. But you can also return a handle to an object, which is what happens here. This method returns a handle to an object of class Soup. The class Soup shows how to prevent direct creation of a class by making all the constructors private. Remember that if you don't explicitly create at least one constructor, the default constructor (a constructor with no arguments) will be created for you. By writing the default constructor, it won't be created automatically. By making it private, no one can create an object of that class. But now how does anyone use this class? The above example shows two options. First, a static method is created that creates a new Soup and returns a handle to it. This could be useful if you want to do some extra operations on the Soup before returning it, or if you want to keep count of how many Soup objects to create (perhaps to restrict their population). The second option uses what's called a design pattern, which will be discussed later in this book. This particular pattern is called a singleton because it allows only a single object to ever be created. The object of class Soup is created as a static private member of Soup, so there's one and only one, and you can't get at it except through the public method access( ). As previously mentioned, if you don't put an access specifier for class access it defaults to friendly. This means that an object of that class can be created by any other class in the package, but not outside the package. Summary In any relationship it's important to have boundaries that are respected by all parties involved. When you create a library, you establish a relationship with the user of that library - the client programmer - who is another programmer, but one putting together an application or using your library to build a bigger library. Without rules, client programmers can do anything they want with all the members of a class, even if you might prefer they don't directly manipulate some of the members. Everything's naked to the world. This chapter looked at how classes are built to form libraries; first, the way a group of classes is packaged within a library, and second, the way the class controls access to its members. It is estimated that a C programming project begins to break down somewhere between 50K and 100K lines of code because C has a single name space so names begin to collide, causing an extra management overhead. In Java, the package keyword, the package naming scheme and the import keyword give you complete control over names, so the issue of name collision is easily avoided. There are two reasons for controlling access to members. The first is to keep users' hands off tools that they shouldn't touch; tools that are necessary for the internal machinations of the data type, but not part of the interface that users need to solve their particular problems. So making methods and fields private is a service to users because they can easily see what's important to them and what they can ignore. It simplifies their understanding of the class. The second and most important reason for access control is to allow the library designer to change the internal workings of the class without worrying about how it will affect the client programmer. You might build a class one way at first, and then discover that restructuring your code will provide much greater speed. If the interface and implementation are clearly separated and protected, you can accomplish this without forcing the user to rewrite their code. Access specifiers in Java give valuable control to the creator of a class. The users of the class can clearly see exactly what they can use and what to ignore. More important, though, is the ability to ensure that no user becomes dependent on any part of the underlying implementation of a class. If you know this as the creator of the class, you can change the underlying implementation with the knowledge that no client programmer will be affected by the changes because they can't access that part of the class. When you have the ability to change the underlying implementation, you can not only improve your design later, but you also have the freedom to make mistakes. No matter how carefully you plan and design you'll make mistakes. Knowing that it's relatively safe to make these mistakes means you'll be more experimental, you'll learn faster and you'll finish your project sooner. The public interface to a class is what the user does see, so that is the most important part of the class to get right during analysis and design. Even that allows you some leeway for change. If you don't get the interface right the first time, you can add more methods, as long as you don't remove any that client programmers have already used in their code. Exercises 1. Create a class with public, private, protected, and friendly data members and method members. Create an object of this class and see what kind of compiler messages you get when you try to access all the class members. Be aware that classes in the same directory are part of the default package. 2. Create a class with protected data. Create a second class in the same file with a method that manipulates the protected data in the first class. 3. Create a new directory and edit your CLASSPATH to include that new directory. Copy the P.class file to your new directory and then change the names of the file, the P class inside and the method names. Would making the Foreign class part of the c05 package change anything? But to be revolutionary, you've got to be able to do a lot more than copy code and change it. That's the approach used in procedural languages like C, and it hasn't worked very well. Like everything in Java, the solution revolves around the class. You reuse code by creating new classes, but instead of creating them from scratch, you use existing classes that someone has already built and debugged. The trick is to use the classes without soiling the existing code. In this chapter you'll see two ways to accomplish this. The first is quite straightforward: You simply create objects of your existing class inside the new class. This is called composition because the new class is composed of objects of existing classes. You're simply reusing the functionality of the code, not its form. The second approach is more subtle. It creates a new class as a type of an existing class. You literally take the form of the existing class and add code to it without modifying the existing class. This magical act is called inheritance, and the compiler does most of the work. Inheritance is one of the cornerstones of object-oriented programming and has additional implications that will be explored in the next chapter. It turns out that much of the syntax and behavior are similar for both composition and inheritance (which makes sense because they are both ways of making new types from existing types). In this chapter, you'll learn about these code reuse mechanisms. Composition syntax Until now, composition has been used quite frequently. You simply place object handles inside new classes. For example, suppose you'd like an object that holds several String objects, a couple of primitives and an object of another class. You will learn later that every non-primitive object has a toString( ) method, and it's called in special situations when the compiler wants a String but it's got one of these objects. So in the expression: System.out.println(source = + source); the compiler sees you trying to add a String object (source = ) to a WaterSource. Any time you want to allow this behavior with a class you create you need only write a toString( ) method. The output of the print statement is in fact: valve1 = null valve2 = null valve3 = null valve4 = null i = 0 f = 0.0 source = null Primitives that are fields in a class are automatically initialized to zero, as noted in Chapter 2. But the object handles are initialized to null, and if you try to call methods for any of them you'll get an exception. It's actually pretty good (and useful) that you can still print them out without throwing an exception. It makes sense that the compiler doesn't just create a default object for every handle because that would incur unnecessary overhead in many cases. If you want the handles initialized, you can do it: 1. At the point the objects are defined. This means that they'll always be initialized before the constructor is called. 2. In the constructor for that class 3. Right before you actually need to use the object. This can reduce overhead, if there are situations where the object doesn't need to be created. When you don't initialize at the point of definition, there's still no guarantee that you'll perform any initialization before you send a message to an object handle - except for the inevitable run-time exception. Inheritance syntax Inheritance is such an integral part of Java (and OOP languages in general) that it was introduced in Chapter 1 and has been used occasionally in chapters before this one because certain situations required it. In addition, you're always doing inheritance when you create a class, because if you don't say otherwise you inherit from Java's standard root class Object. The syntax for composition is obvious, but to perform inheritance there's a distinctly different form. When you do this, you automatically get all the data members and methods in the base class. First, in the Cleanser append( ) method, Strings are concatenated to s using the += operator, which is one of the operators (along with '+') that the Java designers overloaded to work with Strings. Second, both Cleanser and Detergent contain a main( ) method. You can create a main( ) for each one of your classes, and it's often recommended to code this way so that your test code is wrapped in with the class. Even if you have a lot of classes in a program only the main( ) for the public class invoked on the command line will be called. But you can also say java Cleanser to invoke Cleanser.main( ), even though Cleanser is not a public class. This technique of putting a main( ) in each class allows easy unit testing for each class. And you don't need to remove the main( ) when you're finished testing; you can leave it in for later testing. Here, you can see that Detergent.main( ) calls Cleanser.main( ) explicitly. It's important that all of the methods in Cleanser are public. Remember that if you leave off any access specifier the member defaults to friendly, which allows access only to package members. Thus, within this package, anyone could use those methods if there were no access specifier. Detergent would have no trouble, for example. However, if a class from some other package were to inherit Cleanser it could access only public members. So to plan for inheritance, as a general rule make all fields private and all methods public. Note that Cleanser has a set of methods in its interface: append( ), dilute( ), apply( ), scrub( ) and print( ). Because Detergent is derived from Cleanser (via the extends keyword) it automatically gets all these methods in its interface, even though you don't see them all explicitly defined in Detergent. You can think of inheritance, then, as reusing the interface. In this case, you might want to call the method from the base class inside the new version. But inside scrub( ) you cannot simply call scrub( ), since that would produce a recursive call, which isn't what you want. To solve this problem Java has the keyword super that refers to the superclass that the current class has been inherited from. Thus the expression super.scrub( ) calls the base-class version of the method scrub( ). When inheriting you're not restricted to using the methods of the base class. You can also add new methods to the derived class exactly the way you put any method in a class: just define it. The extends keyword suggests that you are going to add new methods to the base-class interface, and the method foam( ) is an example of this. In Detergent.main( ) you can see that for a Detergent object you can call all the methods that are available in Cleanser as well as in Detergent (i.e. foam( )). Initializing the base class Since there are now two classes involved - the base class and the derived class - instead of just one, it can be a bit confusing to try to imagine the resulting object produced by a derived class. From the outside, it looks like the new class has the same interface as the base class and maybe some additional methods and fields. But inheritance doesn't just copy the interface of the base class. When you create an object of the derived class, it contains within it a subobject of the base class. This subobject is the same as if you had created an object of the base class by itself. It's just that, from the outside, the subobject of the base class is wrapped within the derived-class object. Java automatically inserts calls to the base-class constructor in the derived-class constructor. Even if you don't create a constructor for Cartoon( ), the compiler will synthesize a default constructor for you that calls the base class constructor. Constructors with arguments The above example has default constructors; that is, they don't have any arguments. It's easy for the compiler to call these because there's no question about what arguments to pass. In addition, the call to the base-class constructor must be the first thing you do in the derived-class constructor. This means nothing else can appear before it. As you'll see in Chapter 9, this also prevents a derived-class constructor from catching any exceptions that come from a base class. This can be inconvenient at times. Combining composition and inheritance It is very common to use composition and inheritance together. Guaranteeing proper cleanup Java doesn't have the C++ concept of a destructor, a method that is automatically called when an object is destroyed. The reason is probably that in Java the practice is simply to forget about objects rather than to destroy them, allowing the garbage collector to reclaim the memory as necessary. Often this is fine, but there are times when your class might perform some activities during its lifetime that require cleanup. As mentioned in Chapter 4, you can't know when the garbage collector will be called, or if it will be called. So if you want something cleaned up for a class, you must write a special method to do it explicitly, and make sure that the client programmer knows that they must call this method. On top of this, as described in Chapter 9 (exception handling), you must guard against an exception by putting such cleanup in a finally clause. Each class redefines Shape's cleanup( ) method in addition to calling the base-class version of that method using super. The specific Shape classes Circle, Triangle and Line all have constructors that draw, although any method called during the lifetime of the object could be responsible for doing something that needs cleanup. Each class has its own cleanup( ) method to restore non-memory things back to the way they were before the object existed. In main( ), you can see two keywords that are new, and won't officially be introduced until Chapter 9: try and finally. The try keyword indicates that the block that follows (delimited by curly braces) is a guarded region, which means that it is given special treatment. One of these special treatments is that the code in the finally clause following this guarded region is always executed, no matter how the try block exits. Note that in your cleanup method you must also pay attention to the calling order for the base-class and member-object cleanup methods in case one subobject depends on another. There can be many cases in which the cleanup issue is not a problem; you just let the garbage collector do the work. But when you must do it explicitly, diligence and attention is required. Order of garbage collection There's not much you can rely on when it comes to garbage collection. The garbage collector might never be called. If it is, it can reclaim objects in any order it wants. In addition, implementations of the garbage collector in Java 1.0 often don't call the finalize( ) methods. It's best to not rely on garbage collection for anything but memory reclamation. If you want cleanup to take place, make your own cleanup methods and don't rely on finalize( ). If a Java base class has a method name that's overloaded several times, redefining that method name in the derived class will not hide any of the base-class versions. It can be confusing otherwise (which is why C++ disallows it, to prevent you from making what is probably a mistake). Choosing composition vs. You might wonder about the difference between the two, and when to choose one over the other. Composition is generally used when you want the features of an existing class inside your new class, but not its interface. That is, you embed an object so that you can use it to implement features of your new class, but the user of your new class sees the interface you've defined rather than the interface from the embedded object. For this effect, you embed private objects of existing classes inside your new class. Sometimes it makes sense to allow the class user to directly access the composition of your new class; that is, to make the member objects public. The member objects use implementation hiding themselves, so this is a safe thing to do and when the user knows you're assembling a bunch of parts, it makes the interface easier to understand. When you inherit, you take an existing class and make a special version of it. In general, this means that you're taking a general-purpose class and specializing it for a particular need. With a little thought, you'll see that it would make no sense to compose a car using a vehicle object - a car doesn't contain a vehicle, it is a vehicle. The is-a relationship is expressed with inheritance, and the has-a relationship is expressed with composition. In an ideal world, private members would always be hard-and-fast private, but in real projects there are times when you want to make something hidden from the world at large and yet allow access for members of derived classes. The protected keyword is a nod to pragmatism. Incremental development One of the advantages of inheritance is that it supports incremental development by allowing you to introduce new code without causing bugs in existing code. This also isolates new bugs to the new code. By inheriting from an existing, functional class and adding data members and methods (and redefining existing methods), you leave the existing code - that someone else might still be using - untouched and unbugged. If a bug happens, you know that it's in your new code, which is much shorter and easier to read than if you had modified the body of existing code. It's rather amazing how cleanly the classes are separated. You don't even need the source code for the methods in order to reuse the code. At most, you just import a package. You can do as much analysis as you want, but you still won't know all the answers when you set out on a project. You'll have much more success - and more immediate feedback - if you start out to grow your project as an organic, evolutionary creature, rather than constructing it all at once like a glass-box skyscraper. Although inheritance for experimentation can be a useful technique, at some point after things stabilize you need to take a new look at your class hierarchy with an eye to collapsing it into a sensible structure. Upcasting The most important aspect of inheritance is not that it provides methods for the new class. It's the relationship expressed between the new class and the base class. This relationship can be summarized by saying The new class is a type of the existing class. This description is not just a fanciful way of explaining inheritance - it's supported directly by the language. As an example, consider a base class called Instrument that represents musical instruments and a derived class called Wind. Because inheritance means that all of the methods in the base class are also available in the derived class, any message you can send to the base class can also be sent to the derived class. If the Instrument class has a play( ) method, so will Wind instruments. This means we can accurately say that a Wind object is also a type of Instrument. However, in Wind.main( ) the tune( ) method is called by giving it a Wind handle. Inside tune( ), the code works for Instrument and anything derived from Instrument, and the act of converting a Wind handle into an Instrument handle is called upcasting. Why upcasting? The reason for the term is historical and is based on the way class inheritance diagrams have traditionally been drawn with the root at the top of the page, growing downward. Upcasting is always safe because you're going from a more specific type to a more general type. That is, the derived class is a superset of the base class. It might contain more methods than the base class, but it must contain at least the methods in the base class. The only thing that can occur to the class interface during the upcast is that it can lose methods, not gain them. This is why the compiler allows upcasting without any explicit casts or other special notation. You can also perform the reverse of upcasting, called downcasting, but this involves a dilemma that is the subject of Chapter 11. Composition vs. Occasionally, you'll use existing classes to build new classes with composition. Even less frequently than that you'll use inheritance. So although inheritance gets a lot of emphasis while learning OOP, it doesn't mean that you should use it everywhere you possibly can. On the contrary, you should use it sparingly, only when it's clear that inheritance is useful. One of the clearest ways to determine whether you should use composition or inheritance is to ask whether you'll ever need to upcast from your new class to the base class. If you must upcast, then inheritance is necessary, but if you don't need to upcast, then you should look closely at whether you need inheritance. The next chapter (polymorphism) provides one of the most compelling reasons for upcasting, but if you remember to ask Do I need to upcast?, you'll have a good tool for deciding between composition and inheritance. The final keyword The final keyword has slightly different meanings depending on the context, but in general it says This cannot be changed. You might want to prevent changes for two reasons: design or efficiency. Because these two reasons are quite different, it's possible to misuse the final keyword. The following sections discuss the three places where final can be used: for data, methods and for a class. Final data Many programming languages have a way to tell the compiler that a piece of data is constant. A constant is useful for two reasons: 1. It can be a compile-time constant that won't ever change. 2. It can be a value initialized at run-time that you don't want changed. In the case of a compile-time constant the compiler is allowed to fold the constant value into any calculations in which it's used; that is, the calculation can be performed at compile time, eliminating some run-time overhead. In Java, these sorts of constants must be primitives and are expressed using the final keyword. A value must be given at the time of definition of such a constant. A field that is both static and final has only one piece of storage that cannot be changed. When using final with object handles rather than primitives the meaning gets a bit confusing. With a primitive, final makes the value a constant, but with an object handle, final makes the handle a constant. The handle must be initialized to an object at the point of declaration, and the handle can never be changed to point to another object. However, the object can be modified; Java does not provide a way to make any arbitrary object a constant. I3 is the more typical way you'll see such constants defined: public so they're usable outside the package, static to emphasize that there's only one, and final to say that it's a constant. Note that final static primitives with constant initial values (that is, compile-time constants) are named with all capitals by convention. Also note that i5 cannot be known at compile time, so it is not capitalized. Just because something is final doesn't mean that its value is known at compile-time. This is demonstrated by initializing i4 and i5 at run-time using randomly generated numbers. This portion of the example also shows the difference between making a final value static or non-static. This difference shows up only when the values are initialized at run-time, since the compile-time values are treated the same by the compiler. That's because it's static and is initialized once upon loading and not each time a new object is created. The variables v1 through v4 demonstrate the meaning of a final handle. As you can see in main( ), just because v2 is final doesn't mean that you can't change its value. However, you cannot re-bind v2 to a new object, precisely because it's final. That's what final means for a handle. You can also see the same meaning holds true for an array, which is just another kind of handle. Blank finals Java 1.1 allows the creation of blank finals, which are fields that are declared as final but are not given an initialization value. In all cases, the blank final must be initialized before it is used, and the compiler ensures this. However, blank finals provide much more flexibility in the use of the final keyword since, for example, a final field inside a class can now be different for each object and yet it retains its immutable quality. This way it's guaranteed that the final field is always initialized before use. Final arguments Java 1.1 allows you to make arguments final by declaring them as such in the argument list. The methods f( ) and g( ) show what happens when primitive arguments are final: you can only read the argument, but you can't change it. Final methods There are two reasons for final methods. The first is to put a lock on the method to prevent any inheriting class from changing its meaning. This is done for design reasons when you want to make sure that a method's behavior is retained during inheritance and cannot be overridden. The second reason for final methods is efficiency. If you make a method final, you are allowing the compiler to turn any calls to that method into inline calls. This eliminates the overhead of the method call. Of course, if a method is big, then your code begins to bloat and you probably won't see any performance gains from inlining since any improvements will be dwarfed by the amount of time spent inside the method. It is implied that the Java compiler is able to detect these situations and choose wisely whether to inline a final method. However, it's better to not trust that the compiler is able to do this and make a method final only if it's quite small or if you want to explicitly prevent overriding. Any private methods in a class are implicitly final. Because you can't access a private method, you can't override it (the compiler gives an error message if you try). You can add the final specifier to a private method but it doesn't give that method any extra meaning. Final classes When you say that an entire class is final (by preceding its definition with the final keyword), you state that you don't want to inherit from this class or allow anyone else to do so. In other words, for some reason the design of your class is such that there is never a need to make any changes, or for safety or security reasons you don't want subclassing. Alternatively, you might be dealing with an efficiency issue and you want to make sure that any activity involved with objects of this class is as efficient as possible. The same rules apply to final for data members regardless of whether the class is defined as final. Defining the class as final simply prevents inheritance - nothing more. However, because it prevents inheritance all methods in a final class are implicitly final, since there's no way to override them. So the compiler has the same efficiency options as it does if you explicitly declare a method final. You can add the final specifier to a method in a final class, but it doesn't add any meaning. Final caution It can seem to be sensible to make a method final while you're designing a class. You might feel that efficiency is very important when using your class and that no one could possibly want to override your methods anyway. Sometimes this is true. But be careful with your assumptions. In general, it's difficult to anticipate how a class can be reused, especially a general-purpose class. If you define a method as final you might prevent the possibility of reusing your class through inheritance in some other programmer's project simply because you couldn't imagine it being used that way. The standard Java library is a good example of this. In particular, the Vector class is commonly used and might be even more useful if, in the name of efficiency, all the methods hadn't been made final. It's easily conceivable that you might want to inherit and override with such a fundamentally useful class, but the designers somehow decided this wasn't appropriate. This is ironic for two reasons. First, Stack is inherited from Vector, which says that a Stack is a Vector, which isn't really true. This lends credence to the theory that programmers are consistently bad at guessing where optimizations should occur. It's just too bad that such a clumsy design made it into the standard library where we must all cope with it. It's also interesting to note that Hashtable, another important standard library class, does not have any final methods. As mentioned elsewhere in this book, it's quite obvious that some classes were designed by completely different people than others. When things are inconsistent it just makes more work for the user. Yet another paean to the value of design and code walkthroughs. Initialization and class loading In many more traditional languages, programs are loaded all at once as part of the startup process. This is followed by initialization, and then the program begins. The process of initialization in these languages must be carefully controlled so that the order of initialization of statics doesn't cause trouble. C++, for example, has problems if one static expects another static to be valid before the second one has been initialized. Java doesn't have this problem because it takes a different approach to loading. Because everything in Java is an object, many activities become easier, and this is one of them. As you will learn in the next chapter, the code for each object exists in a separate file. That file isn't loaded until the code is needed. In general, you can say that until an object of that class is constructed, the class code doesn't get loaded. Since there can be some subtleties with static methods, you can also say, Class code is loaded at the point of first use. The point of first use is also where the static initialization takes place. All the static objects and the static code block will be initialized in textual order (that is, the order that you write them down in the class definition) at the point of loading. The statics, of course, are initialized only once. Initialization with inheritance It's helpful to look at the whole initialization process, including inheritance, to get a full picture of what happens. In the process of loading it, the loader notices that it has a base class (that's what the extends keyword says), which it then loads. This will happen whether or not you're going to make an object of that base class. Next, the static initialization in the root base class (in this case, Insect) is performed, and then the next derived class, and so on. This is important because the derived-class static initialization might depend on the base class member being initialized properly. At this point, the necessary classes have all been loaded so the object can be created. First, all the primitives in this object are set to their default values and the object handles are set to null. Then the base-class constructor will be called. In this case the call is automatic, but you can also specify the constructor call (as the first operation in the Beetle( ) constructor) using super. The base class construction goes through the same process in the same order as the derived-class constructor. After the base-class constructor completes, the instance variables are initialized in textual order. Finally, the rest of the body of the constructor is executed. Summary Both inheritance and composition allow you to create a new type from existing types. Typically, however, you use composition to reuse existing types as part of the underlying implementation of the new type and inheritance when you want to reuse the interface. Since the derived class has the base-class interface, it can be upcast to the base, which is critical for polymorphism, as you'll see in the next chapter. Despite the strong emphasis on inheritance in object-oriented programming, when you start a design you should generally prefer composition during the first cut and use inheritance only when it is clearly necessary. In addition, by using the added artifice of inheritance with your member type, you can change the exact type, and thus the behavior, of those member objects at run-time. Therefore, you can change the behavior of the composed object at run-time. Although code reuse through composition and inheritance is helpful for rapid project development, you'll generally want to redesign your class hierarchy before allowing other programmers to become dependent on it. Your goal is a hierarchy in which each class has a specific use and is neither too big (encompassing so much functionality that it's unwieldy to reuse) nor annoyingly small (you can't use it by itself or without adding functionality). Your finished classes should be easily reused. Exercises 1. Create two classes, A and B, with default constructors (empty argument lists) that announce themselves. Inherit a new class called C from A, and create a member B inside C. Do not create a constructor for C. Create an object of class C and observe the results. 2. Modify Exercise 1 so that A and B have constructors with arguments instead of default constructors. Write a constructor for C and perform all initialization within C's constructor. 3. Take the file Cartoon.java and comment out the constructor for the Cartoon class. Explain what happens. 4. Take the file Chess.java and comment out the constructor for the Chess class. Explain what happens. It provides another dimension of separation of interface from implementation, to decouple what from how. Polymorphism allows improved code organization and readability as well as the creation of extensible programs that can be grown not only during the original creation of the project but also when new features are desired. Encapsulation creates new data types by combining characteristics and behaviors. Implementation hiding separates the interface from the implementation by making the details private. This sort of mechanical organization makes ready sense to someone with a procedural programming background. But polymorphism deals with decoupling in terms of types. In the last chapter, you saw how inheritance allows the treatment of an object as its own type or its base type. This ability is critical because it allows many types (derived from the same base type) to be treated as if they were one type, and a single piece of code to work on all those different types equally. The polymorphic method call allows one type to express its distinction from another, similar type, as long as they're both derived from the same base type. This distinction is expressed through differences in behavior of the methods you can call through the base class. In this chapter, you'll learn about polymorphism (also called dynamic binding or late binding or run-time binding) starting from the basics, with simple examples that strip away everything but the polymorphic behavior of the program. Upcasting In Chapter 6 you saw how an object can be used as its own type or as an object of its base type. Taking an object handle and treating it as the handle of the base type is called upcasting because of the way inheritance trees are drawn with the base class at the top. In main( ), you can see this happening as a Wind handle is passed to tune( ), with no cast necessary. This is acceptable; the interface in Instrument must exist in Wind, because Wind is inherited from Instrument. Upcasting from Wind to Instrument may narrow that interface, but it cannot make it anything less than the full interface to Instrument. Why upcast? This program might seem strange to you. Why should anyone intentionally forget the type of an object? This is what happens when you upcast, and it seems like it could be much more straightforward if tune( ) simply takes a Wind handle as its argument. This brings up an essential point: If you did that, you'd need to write a new tune( ) for every type of Instrument in your system. This means more programming in the first place, but it also means that if you want to add a new method like tune( ) or a new type of Instrument, you've got a lot of work to do. Add the fact that the compiler won't give you any error messages if you forget to overload one of your methods and the whole process of working with types becomes unmanageable. Wouldn't it be much nicer if you could just write a single method that takes the base class as its argument, and not any of the specific derived classes? That is, wouldn't it be nice if you could forget that there are derived classes, and write your code to talk only to the base class? That's exactly what polymorphism allows you to do. However, most programmers (who come from a procedural programming background) have a bit of trouble with the way polymorphism works. The twist The difficulty with Music.java can be seen by running the program. The output is Wind.play( ). This is clearly the desired output, but it doesn't seem to make sense that it would work that way. So how can the compiler possibly know that this Instrument handle points to a Wind in this case and not a Brass or Stringed? The compiler can't. To get a deeper understanding of the issue, it's useful to examine the subject of binding. Method call binding Connecting a method call to a method body is called binding. When binding is performed before the program is run (by the compiler and linker, if there is one), it's called early binding. You might not have heard the term before because it has never been an option with procedural languages. C compilers have only one kind of method call, and that's early binding. The confusing part of the above program revolves around early binding because the compiler cannot know the correct method to call when it has only an Instrument handle. The solution is called late binding, which means that the binding occurs at run-time based on the type of object. Late binding is also called dynamic binding or run-time binding. When a language implements late binding, there must be some mechanism to determine the type of the object at run-time and to call the appropriate method. That is, the compiler still doesn't know the object type, but the method-call mechanism finds out and calls the correct method body. The late-binding mechanism varies from language to language, but you can imagine that some sort of type information must be installed in the objects. All method binding in Java uses late binding unless a method has been declared final. This means that you ordinarily don't need to make any decisions about whether late binding will occur - it happens automatically. Why would you declare a method final? As noted in the last chapter, it prevents anyone from overriding that method. Perhaps more importantly, it effectively turns off dynamic binding, or rather it tells the compiler that dynamic binding isn't necessary. This allows the compiler to generate more efficient code for final method calls. Or to put it another way, you send a message to an object and let the object figure out the right thing to do. The classic example in OOP is the shape example. This is commonly used because it is easy to visualize, but unfortunately it can confuse novice programmers into thinking that OOP is just for graphics programming, which is of course not the case. The shape example has a base class called Shape and various derived types: Circle, Square, Triangle, etc. The reason the example works so well is that it's easy to say a circle is a type of shape and be understood. So the compiler agrees with the statement and doesn't issue an error message. And yet the proper Circle.draw( ) is called because of late binding (polymorphism). The derived classes override these definitions to provide unique behavior for each specific type of shape. The main class Shapes contains a static method randShape( ) that produces a handle to a randomly-selected Shape object each time you call it. Note that the upcasting happens in each of the return statements, which take a handle to a Circle, Square, or Triangle and send it out of the method as the return type, Shape. So whenever you call this method you never get a chance to see what specific type it is, since you always get back a plain Shape handle. At this point you know you have Shapes, but you don't know anything more specific than that (and neither does the compiler). The point of choosing the shapes randomly is to drive home the understanding that the compiler can have no special knowledge that allows it to make the correct calls at compile time. All the calls to draw( ) are made through dynamic binding. Extensibility Now let's return to the musical instrument example. Because of polymorphism, you can add as many new types as you want to the system without changing the tune( ) method. In a well-designed OOP program, most or all of your methods will follow the model of tune( ) and communicate only with the base-class interface. Such a program is extensible because you can add new functionality by inheriting new data types from the common base class. The methods that manipulate the base-class interface will not need to be changed at all to accommodate the new classes. Consider what happens if you take the instrument example and add more methods in the base class and a number of new classes. Here's the diagram: All these new classes work correctly with the old, unchanged tune( ) method. Even if tune( ) is in a separate file and new methods are added to the interface of Instrument, tune( ) works correctly without recompilation. In main( ), when you place something inside the Instrument3 array you automatically upcast to Instrument3. You can see that the tune( ) method is blissfully ignorant of all the code changes that have happened around it, and yet it works correctly. This is exactly what polymorphism is supposed to provide. Your code changes don't cause damage to parts of the program that should not be affected. Put another way, polymorphism is one of the most important techniques that allow the programmer to separate the things that change from the things that stay the same. Overriding vs. In the following program, the interface of the method play( ) is changed in the process of overriding it, which means that you haven't overridden the method, but instead overloaded it. The compiler allows you to overload methods so it gives no complaint. But the behavior is probably not what you want. In InstrumentX, the play( ) method takes an int that has the identifier NoteX. That is, even though NoteX is a class name, it can also be used as an identifier without complaint. But in WindX, play( ) takes a NoteX handle that has an identifier n. The compiler, however, assumed that an overload and not an override was intended. Note that if you follow the standard Java naming convention, the argument identifier would be noteX, which would distinguish it from the class name. In tune, the InstrumentX i is sent the play( ) message, with one of NoteX's members (MIDDLE_C) as an argument. Since NoteX contains int definitions, this means that the int version of the now-overloaded play( ) method is called, and since that has not been overridden the base-class version is used. The output is: InstrumentX.play() This certainly doesn't appear to be a polymorphic method call. Once you understand what's happening, you can fix the problem fairly easily, but imagine how difficult it might be to find the bug if it's buried in a program of significant size. Abstract classes and methods In all the instrument examples, the methods in the base class Instrument were always dummy methods. If these methods are ever called, you've done something wrong. That's because the intent of Instrument is to create a common interface for all the classes derived from it. The only reason to establish this common interface is so it can be expressed differently for each different subtype. It establishes a basic form, so you can say what's in common with all the derived classes. Another way of saying this is to call Instrument an abstract base class (or simply an abstract class). You create an abstract class when you want to manipulate a set of classes through this common interface. All derived-class methods that match the signature of the base-class declaration will be called using the dynamic binding mechanism. That is, Instrument is meant to express only the interface, and not a particular implementation, so creating an Instrument object makes no sense, and you'll probably want to prevent the user from doing it. This can be accomplished by making all the methods in Instrument print error messages, but this delays the information until run-time and requires reliable exhaustive testing on the user's part. It's always better to catch problems at compile time. Java provides a mechanism for doing this called the abstract method. This is a method that is incomplete; it has only a declaration and no method body. Here is the syntax for an abstract method declaration: abstract void X(); A class containing abstract methods is called an abstract class. If a class contains one or more abstract methods, the class must be qualified as abstract. It cannot safely create an object of an abstract class, so you get an error message from the compiler. This way the compiler ensures the purity of the abstract class, and you don't need to worry about misusing it. If you inherit from an abstract class and you want to make objects of the new type, you must provide method definitions for all the abstract methods in the base class. If you don't (and you may choose not to), then the derived class is also abstract and the compiler will force you to qualify that class with the abstract keyword. It's possible to declare a class as abstract without including any abstract methods. This is useful when you've got a class in which it doesn't make sense to have any abstract methods, and yet you want to prevent any instances of that class. The Instrument class can easily be turned into an abstract class. Only some of the methods will be abstract, since making a class abstract doesn't force you to make all the methods abstract. It's helpful to create abstract classes and methods because they make the abstractness of a class explicit and tell both the user and the compiler how it was intended to be used. Interfaces The interface keyword takes the abstract concept one step further. You could think of it as a pure abstract class. It allows the creator to establish the form for a class: method names, argument lists and return types, but no method bodies. An interface can also contain data members of primitive types, but these are implicitly static and final. An interface provides only a form, but no implementation. An interface says: This is what all classes that implement this particular interface will look like. Thus, any code that uses a particular interface knows what methods might be called for that interface, and that's all. So the interface is used to establish a protocol between classes. Like a class, you can add the public keyword before the interface keyword (but only if that interface is defined in a file of the same name) or leave it off to give friendly status. To make a class that conforms to a particular interface (or group of interfaces) use the implements keyword. You're saying The interface is what it looks like and here's how it works. Other than that, it bears a strong resemblance to inheritance. The diagram for the instrument example shows this: Once you've implemented an interface, that implementation becomes an ordinary class that can be extended in the regular way. You can choose to explicitly declare the method declarations in an interface as public. But they are public even if you don't say it. So when you implement an interface, the methods from the interface must be defined as public. Otherwise they would default to friendly and you'd be restricting the accessibility of a method during inheritance, which is not allowed by the Java compiler. You can see this in the modified version of the Instrument example. Note that every method in the interface is strictly a declaration, which is the only thing the compiler allows. It doesn't matter if you are upcasting to a regular class called Instrument5, an abstract class called Instrument5, or to an interface called Instrument5. The behavior is the same. In fact, you can see in the tune( ) method that there isn't any evidence about whether Instrument5 is a regular class, an abstract class or an interface. This is the intent: Each approach gives the programmer different control over the way objects are created and used. Multiple inheritance in Java The interface isn't simply a more pure form of abstract class. It has a higher purpose than that. Because an interface has no implementation at all - that is, there is no storage associated with an interface - there's nothing to prevent many interfaces from being combined. If you do inherit from a non-interface, you can inherit from only one. All the rest of the base elements must be interfaces. You place all the interface names after the implements keyword and separate them with commas. You can have as many interfaces as you want and each one becomes an independent type that you can upcast to. When you combine a concrete class with interfaces this way, the concrete class must come first, then the interfaces. The rule for an interface is that you can inherit from it (as you will see shortly), but then you've got another interface. If you want to create an object of the new type, it must be a class with all definitions provided. Even though Hero does not explicitly provide a definition for fight( ), the definition comes along with ActionCharacter so it is automatically provided and it's possible to create objects of Hero. In class Adventure, you can see that there are four methods that take as arguments the various interfaces and the concrete class. When a Hero object is created, it can be passed to any of these methods, which means it is being upcast to each interface in turn. Because of the way interfaces are designed in Java, this works without a hitch and without any particular effort on the part of the programmer. Keep in mind that the core reason for interfaces is shown in the above example: to be able to upcast to more than one base type. However, a second reason for using interfaces is the same as using an abstract base class: to prevent the client programmer from making an object of this class and to establish that it is only an interface. This brings up a question: Should you use an interface or an abstract class? In fact, if you know something is going to be a base class, your first choice should be to make it an interface, and only if you're forced to have method definitions or member variables should you change to an abstract class. Extending an interface with inheritance You can easily add new method declarations to an interface using inheritance, and you can also combine several interfaces into a new interface with inheritance. This is implemented in DragonZilla. The syntax used in Vampire works only when inheriting interfaces. Normally, you can use extends with only a single class, but since an interface can be made from multiple other interfaces, extends can refer to multiple base interfaces when building a new interface. As you can see, the interface names are simply separated with commas. The fields in an interface are automatically public, so it's unnecessary to specify that. Now you can use the constants from outside the package by importing c07.* or c07.Months just as you would with any other package, and referencing the values with expressions like Months.JANUARY. Of course, what you get is just an int so there isn't the extra type safety that C++'s enum has, but this (commonly-used) technique is certainly an improvement over hard-coding numbers into your programs. It's a final class with a private constructor so no one can inherit from it or make any instances of it. The only instances are the final static ones created in the class itself: JAN, FEB, MAR, etc. These objects are also used in the array month, which lets you choose months by number instead of by name. The previous example Months.java provided only int values, so an int variable intended to represent a month could actually be given any integer value, which wasn't too safe. This approach also allows you to use == or equals( ) interchangeably, as shown at the end of main( ). Initializing fields in interfaces Fields defined in interfaces are automatically static and final. These cannot be blank finals, but they can be initialized with non-constant expressions. Inner classes In Java 1.1 it's possible to place a class definition within another class definition. This is called an inner class. The inner class is a useful feature because it allows you to group classes that logically belong together and to control the visibility of one within the other. However, it's important to understand that inner classes are distinctly different from composition. Often, while you're learning about them, the need for inner classes isn't immediately obvious. At the end of this section, after all of the syntax and semantics of inner classes have been described, you'll find an example that should make clear the benefits of inner classes. Here, the only practical difference is that the names are nested within Parcel1. You'll see in a while that this isn't the only difference. Inner classes and upcasting So far, inner classes don't seem that dramatic. After all, if it's hiding you're after, Java already has a perfectly good hiding mechanism - just allow the class to be friendly (visible only within a package) rather than creating it as an inner class. However, inner classes really come into their own when you start upcasting to a base class, and in particular to an interface. In Parcel3, something new has been added: the inner class PContents is private so no one but Parcel3 can access it. PDestination is protected, so no one but Parcel3, classes in the Parcel3 package (since protected also gives package access; that is, protected is also friendly), and the inheritors of Parcel3 can access PDestination. This means that the client programmer has restricted knowledge and access to these members. In fact, you can't even downcast to a private inner class (or a protected inner class unless you're an inheritor), because you can't access the name, as you can see in class Test. Thus, the private inner class provides a way for the class designer to completely prevent any type-coding dependencies and to completely hide details about implementation. In addition, extension of an interface is useless from the client programmer's perspective since the client programmer cannot access any additional methods that aren't part of the public interface class. This also provides an opportunity for the Java compiler to generate more efficient code. Normal (non-inner) classes cannot be made private or protected - only public or friendly. Note that Contents doesn't need to be an abstract class. You could use an ordinary class here as well, but the most typical starting point for such a design is an interface. Inner classes in methods and scopes What you've seen so far encompasses the typical use for inner classes. In general, the code that you'll write and read involving inner classes will be plain inner classes that are simple and easy to understand. However, the design for inner classes is quite complete and there are a number of other, more obscure, ways that you can use them if you choose: inner classes can be created within a method or even an arbitrary scope. There are two reasons for doing this: 1. As shown previously, you're implementing an interface of some kind so that you can create and return a handle. 2. You're solving a complicated problem and you want to create a class to aid in your solution, but you don't want it publicly available. Notice the upcasting that occurs in the return statement - nothing comes out of dest( ) except a handle to the base class Destination. Of course, the fact that the name of the class PDestination is placed inside dest( ) doesn't mean that PDestination is not a valid object once dest( ) returns. This does not mean that the class is conditionally created - it gets compiled along with everything else. However, it's not available outside the scope in which it is defined. Other than that, it looks just like an ordinary class. In addition, the class is anonymous - it has no name. An anonymous class cannot have a constructor where you would normally call super( ). In both of the previous examples, the semicolon doesn't mark the end of the class body (as it does in C++). Instead, it marks the end of the expression that happens to contain the anonymous class. Thus, it's identical to the use of the semicolon everywhere else. What happens if you need to perform some kind of initialization for an object of an anonymous inner class? Since it's anonymous, there's no name to give the constructor so you can't have a constructor. This is why the argument to dest( ) is final. If you forget, you'll get a compile-time error message. As long as you're simply assigning a field, the above approach is fine. But what if you need to perform some constructor-like activity? So in effect, an instance initializer is the constructor for an anonymous inner class. Of course, it's limited; you can't overload instance initializers so you can have only one of these constructors. The link to the outer class So far, it appears that inner classes are just a name-hiding and code-organization scheme, which is helpful but not totally compelling. However, there's another twist. When you create an inner class, objects of that inner class have a link to the enclosing object that made them, and so they can access the members of that enclosing object - without any special qualifications. You call add( ) to add a new Object to the end of the sequence (if there's room left). To fetch each of the objects in a Sequence, there's an interface called Selector, which allows you to see if you're at the end( ), to look at the current( ) Object, and to move to the next( ) Object in the Sequence. Because Selector is an interface, many other classes can implement the interface in their own ways, and many methods can take the interface as an argument, in order to create generic code. Here, the SSelector is a private class that provides Selector functionality. In main( ), you can see the creation of a Sequence, followed by the addition of a number of String objects. Then, a Selector is produced with a call to getSelector( ) and this is used to move through the Sequence and select each item. At first, the creation of SSelector looks like just another inner class. But examine it closely. Note that each of the methods end( ), current( ), and next( ) refer to o, which is a handle that isn't part of SSelector, but is instead a private field in the enclosing class. However, the inner class can access methods and fields from the enclosing class as if they owned them. This turns out to be very convenient, as you can see in the above example. So an inner class has access to the members of the enclosing class. How can this happen? The inner class must keep a reference to the particular object of the enclosing class that was responsible for creating it. Then when you refer to a member of the enclosing class, that (hidden) reference is used to select that member. Fortunately, the compiler takes care of all these details for you, but you can also understand now that an object of an inner class can be created only in association with an object of the enclosing class. The process of construction requires the initialization of the handle to the object of the enclosing class, and the compiler will complain if it cannot access the handle. Most of the time this occurs without any intervention on the part of the programmer. This is not true, however, when you say an inner class is static. A static inner class means: 1. You don't need an outer-class object in order to create an object of a static inner class. 2. You can't access an outer-class object from an object of a static inner class. There are some restrictions: static members can be at only the outer level of a class, so inner classes cannot have static data or static inner classes. If you don't need to create an object of the outer class in order to create an object of the inner class, you can make everything static. Normally you can't put any code inside an interface, but a static inner class can be part of an interface. One drawback to this is the amount of extra code you must carry around. You can use this class for testing, but you don't need to include it in your shipping product. Referring to the outer class object If you need to produce the handle to the outer class object, you name the outer class followed by a dot and this. For example, in the class Sequence.SSelector, any of its methods can produce the stored handle to the outer class Sequence by saying Sequence.this. The resulting handle is automatically the correct type. This is because the object of the inner class is quietly connected to the object of the outer class that it was made from. However, if you make a static inner class, then it doesn't need a handle to the outer class object. Inheriting from inner classes Because the inner class constructor must attach to a handle of the enclosing class object, things are slightly complicated when you inherit from an inner class. The problem is that the secret handle to the enclosing class object must be initialized, and yet in the derived class there's no longer a default object to attach to. But when it comes time to create a constructor, the default one is no good and you can't just pass a handle to an enclosing object. In addition, you must use the syntax enclosingClassHandle.super(); inside the constructor. This provides the necessary handle and the program will then compile. Can inner classes be overridden? What happens when you create an inner class, then inherit from the enclosing class and redefine the inner class? That is, is it possible to override an inner class? You might think that since a BigEgg is being created, the overridden version of Yolk would be used, but this is not the case. The output is: New Egg() Egg.Yolk() This example simply shows that there isn't any extra inner class magic going on when you inherit from the outer class. The method insertYolk( ) allows BigEgg2 to upcast one of its own Yolk objects into the y handle in Egg2, so when g( ) calls y.f( ) the overridden version of f( ) is used. The output is: Egg2.Yolk() New Egg2() Egg2.Yolk() BigEgg2.Yolk() BigEgg2.Yolk.f() The second call to Egg2.Yolk( ) is the base-class constructor call of the BigEgg2.Yolk constructor. You can see that the overridden version of f( ) is used when g( ) is called. For example, the .class files created by InheritInner.java include: InheritInner.class WithInner$Inner.class WithInner.class If inner classes are anonymous, the compiler simply starts generating numbers as inner class identifiers. If inner classes are nested within inner classes, their names are simply appended after a '$' and the outer class identifier(s). Although this scheme of generating internal names is simple and straightforward, it's also robust and handles most situations.3 Since it is the standard naming scheme for Java, the generated files are automatically platform-independent. Why did Sun go to so much trouble to add such a fundamental language feature in Java 1.1? The answer is something that I will refer to here as a control framework. An application framework is a class or a set of classes that's designed to solve a particular type of problem. To apply an application framework, you inherit from one or more classes and override some of the methods. The code you write in the overridden methods customizes the general solution provided by that application framework to solve your specific problem. The control framework is a particular type of application framework dominated by the need to respond to events; a system that primarily responds to events is called an event-driven system. One of the most important problems in application programming is the graphical user interface (GUI), which is almost entirely event-driven. As you will see in Chapter 13, the Java 1.1 AWT is a control framework that elegantly solves the GUI problem using inner classes. What follows is a control framework that contains no specific information about what it's controlling. First, here is the interface that describes any control event. Of course, ready( ) could be overridden in a derived class to base the Event on something other than time. The next file contains the actual control framework that manages and fires events. The first class is really just a helper class whose job is to hold Event objects. The index is used to keep track of the next available space, and next is used when you're looking for the next Event in the list, to see whether you've looped around. This is important during a call to getNext( ), because Event objects are removed from the list (using removeCurrent( )) once they're run, so getNext( ) will encounter holes in the list as it moves through it. Note that removeCurrent( ) doesn't just set some flag indicating that the object is no longer in use. Instead, it sets the handle to null. This is important because if the garbage collector sees a handle that's still in use then it can't clean up the object. If you think your handles might hang around (as they would here), then it's a good idea to set them to null to give the garbage collector permission to clean them up. Controller is where the actual work goes on. It uses an EventSet to hold its Event objects, and addEvent( ) allows you to add new events to this list. But the important method is run( ). This method loops through the EventSet, hunting for an Event object that's ready( ) to run. For each one it finds ready( ), it calls the action( ) method, prints out the description( ), and then removes the Event from the list. Note that so far in this design you know nothing about exactly what an Event does. This is where inner classes come into play. They allow two things: 1. To express the entire implementation of a control-framework application in a single class, thereby encapsulating everything that's unique about that implementation. Inner classes are used to express the many different kinds of action( ) necessary to solve the problem. In addition, the following example uses private inner classes so the implementation is completely hidden and can be changed with impunity. 2. Inner classes keep this implementation from becoming awkward, since you're able to easily access any of the members in the outer class. Without this ability the code might become unpleasant enough that you'd end up seeking an alternative. Consider a particular implementation of the control framework designed to control greenhouse functions.4 Each action is entirely different: turning lights, water, and thermostats on and off, ringing bells, and restarting the system. But the control framework is designed to easily isolate this different code. For each type of action you inherit a new Event inner class, and write the control code inside of action( ). Also, most of the action( ) methods also involve some sort of hardware control, which would most likely involve calls to non-Java code. Most of the Event classes look similar, but Bell and Restart are special. Bell rings, and if it hasn't yet rung enough times it adds a new Bell object to the event list, so it will ring again later. Notice how inner classes almost look like multiple inheritance: Bell has all the methods of Event and it also appears to have all the methods of the outer class GreenhouseControls. Restart is responsible for initializing the system, so it adds all the appropriate events. Of course, a more flexible way to accomplish this is to avoid hard-coding the events and instead read them from a file. And all you need to do in main( ) is create a GreenhouseControls object and add a Restart object to get it going. This example should move you a long way toward appreciating the value of inner classes, especially when used within a control framework. However, in the latter half of Chapter 13 you'll see how elegantly inner classes are used to describe the actions of a graphical user interface. By the time you finish that section you should be fully convinced. Constructors and polymorphism As usual, constructors are different from other kinds of methods. This is also true when polymorphism is involved. Even though constructors are not polymorphic (although you can have a kind of virtual constructor, as you will see in Chapter 11), it's important to understand the way constructors work in complex hierarchies and with polymorphism. This understanding will help you avoid unpleasant entanglements. Order of constructor calls The order of constructor calls was briefly discussed in Chapter 4, but that was before inheritance and polymorphism were introduced. A constructor for the base class is always called in the constructor for a derived class, chaining upward so that a constructor for every base class is called. This makes sense because the constructor has a special job: to see that the object is built properly. A derived class has access to its own members only, and not to those of the base class (whose members are typically private). Only the base-class constructor has the proper knowledge and access to initialize its own elements. Therefore, it's essential that all constructors get called, otherwise the entire object wouldn't be constructed properly. That's why the compiler enforces a constructor call for every portion of a derived class. It will silently call the default constructor if you don't explicitly call a base-class constructor in the derived-class constructor body. If there is no default constructor, the compiler will complain. The important class is Sandwich, which reflects three levels of inheritance (four, if you count the implicit inheritance from Object) and three member objects. This step is repeated recursively such that the root of the hierarchy is constructed first, followed by the next-derived class, etc., until the most-derived class is reached. 2. Member initializers are called in the order of declaration. 3. The body of the derived-class constructor is called. The order of the constructor calls is important. When you inherit, you know all about the base class and can access any public and protected members of the base class. This means that you must be able to assume that all the members of the base class are valid when you're in the derived class. In a normal method, construction has already taken place, so all the members of all parts of the object have been built. Inside the constructor, however, you must be able to assume that all members that you use have been built. The only way to guarantee this is for the base-class constructor to be called first. Then when you're in the derived-class constructor, all the members you can access in the base class have been initialized. If you follow this practice, you will help ensure that all base class members and member objects of the current object have been initialized. Unfortunately, this doesn't handle every case, as you will see in the next section. Inheritance and finalize( ) When you use composition to create a new class, you never worry about finalizing the member objects of that class. Each member is an independent object and thus is garbage collected and finalized regardless of whether it happens to be a member of your class. With inheritance, however, you must override finalize( ) in the derived class if you have any special cleanup that must happen as part of garbage collection. When you override finalize( ) in an inherited class, it's important to remember to call the base-class version of finalize( ), since otherwise the base-class finalization will not happen. This flag is set based on a command-line argument, so you can view the behavior with and without base-class finalization. Each class in the hierarchy also contains a member object of class Characteristic. You will see that regardless of whether the base class finalizers are called, the Characteristic member objects are always finalized. Each overridden finalize( ) must have access to at least protected members since the finalize( ) method in class Object is protected and the compiler will not allow you to reduce the access during inheritance. Remember that garbage collection and in particular finalization might not happen for any particular object so to enforce this, System.runFinalizersOnExit(true) adds the extra overhead to guarantee that finalization takes place. Without base-class finalization, the output is: not finalizing bases Creating Characteristic is alive LivingCreature() Creating Characteristic has heart Animal() Creating Characteristic can live in water Amphibian() Frog() bye! Frog finalize finalizing Characteristic is alive finalizing Characteristic has heart finalizing Characteristic can live in water You can see that, indeed, no finalizers are called for the base classes of Frog. But if you add the finalize argument on the command line, you get: Creating Characteristic is alive LivingCreature() Creating Characteristic has heart Animal() Creating Characteristic can live in water Amphibian() Frog() bye! With base classes, however, you have control over the order of finalization. The best order to use is the one that's shown here, which is the reverse of the order of initialization. Following the form that's used in C++ for destructors, you should perform the derived-class finalization first, then the base-class finalization. That's because the derived-class finalization could call some methods in the base class that require that the base-class components are still alive, so you must not destroy them prematurely. Behavior of polymorphic methods inside constructors The hierarchy of constructor calls brings up an interesting dilemma. What happens if you're inside a constructor and you call a dynamically-bound method of the object being constructed? Inside an ordinary method you can imagine what will happen - the dynamically-bound call is resolved at run-time because the object cannot know whether it belongs to the class the method is in or some class derived from it. For consistency, you might think this is what should happen inside constructors. This is not exactly the case. If you call a dynamically-bound method inside a constructor, the overridden definition for that method is used. However, the effect can be rather unexpected, and can conceal some difficult-to-find bugs. Conceptually, the constructor's job is to bring the object into existence (which is hardly an ordinary feat). Inside any constructor, the entire object might be only partially formed - you can know only that the base-class objects have been initialized, but you cannot know which classes are inherited from you. A dynamically-bound method call, however, reaches forward or outward into the inheritance hierarchy. It calls a method in a derived class. If you do this inside a constructor, you call a method that might manipulate members that haven't been initialized yet - a sure recipe for disaster. Indeed, you are forced to override it in RoundGlyph. But the Glyph constructor calls this method, and the call ends up in RoundGlyph.draw( ), which would seem to be the intent. But look at the output: Glyph() before draw() RoundGlyph.draw(), radius = 0 Glyph() after draw() RoundGlyph.RoundGlyph(), radius = 5 When Glyph's constructor calls draw( ), the value of radius isn't even the default initial value 1. It's zero. This would probably result in either a dot or nothing at all being drawn on the screen, and you'd be staring, trying to figure out why the program won't work. The order of initialization described in the previous section isn't quite complete, and that's the key to solving the mystery. The actual process of initialization is: 1. The storage allocated for the object is initialized to binary zero before anything else happens. 2. The base-class constructors are called as described previously. At this point, the overridden draw( ) method is called, (yes, before the RoundGlyph constructor is called), which discovers a radius value of zero, due to step 1. 3. Member initializers are called in the order of declaration. 4. The body of the derived-class constructor is called. There's an upside to this, which is that everything is at least initialized to zero (or whatever zero means for that particular data type) and not just left as garbage. This includes object handles that are embedded inside a class via composition. So if you forget to initialize that handle you'll get an exception at run time. Everything else gets zero, which is usually a telltale value when looking at output. On the other hand, you should be pretty horrified at the outcome of this program. You've done a perfectly logical thing and yet the behavior is mysteriously wrong, with no complaints from the compiler. Designing with inheritance Once you learn about polymorphism, it can seem that everything ought to be inherited because polymorphism is such a clever tool. This can burden your designs; in fact if you choose inheritance first when you're using an existing class to make a new class things can become needlessly complicated. A better approach is to choose composition first, when it's not obvious which one you should use. Composition does not force a design into an inheritance hierarchy. But composition is also more flexible since it's possible to dynamically choose a type (and thus behavior) when using composition, whereas inheritance requires an exact type to be known at compile time. This means go( ) produces a particular behavior. But since a handle can be re-bound to a different object at run time, a handle for a SadActor object can be substituted in a and then the behavior produced by go( ) changes. Thus you gain dynamic flexibility at run time. In contrast, you can't decide to inherit differently at run time; that must be completely determined at compile time. In this case, that change in state happens to produce a change in behavior. Pure inheritance vs. Inheritance guarantees that any derived class will have the interface of the base class and nothing less. If you follow the above diagram, derived classes will also have no more than the base class interface. All you need to do is upcast from the derived class and never look back to see what exact type of object you're dealing with. Everything is handled through polymorphism. When you see it this way, it seems like a pure is-a relationship is the only sensible way to do things, and any other design indicates muddled thinking and is by definition broken. This too is a trap. As soon as you start thinking this way, you'll turn around and discover that extending the interface (which, unfortunately, the keyword extends seems to promote) is the perfect solution to a particular problem. The following sections show how this is done. However, you know an upcast is always safe; the base class cannot have a bigger interface than the derived class, therefore every message you send through the base class interface is guaranteed to be accepted. But with a downcast, you don't really know that a shape (for example) is actually a circle. It could instead be a triangle or square or some other type. To solve this problem there must be some way to guarantee that a downcast is correct, so you won't accidentally cast to the wrong type and then send a message that the object can't accept. This would be quite unsafe. In some languages (like C++) you must perform a special operation in order to get a type-safe downcast, but in Java every cast is checked! So even though it looks like you're just performing an ordinary parenthesized cast, at run time this cast is checked to ensure that it is in fact the type you think it is. If it isn't, you get a ClassCastException. This act of checking types at run time is called run-time type identification (RTTI). But since it's inherited, it can also be upcast to a Useful. You can see this happening in the initialization of the array x in main( ). Since both objects in the array are of class Useful, you can send the f( ) and g( ) methods to both, and if you try to call u( ) (which exists only in MoreUseful) you'll get a compile-time error message. If you want to access the extended interface of a MoreUseful object, you can try to downcast. If it's the correct type, it will be successful. Otherwise, you'll get a ClassCastException. You don't need to write any special code for this exception, since it indicates a programmer error that could happen anywhere in a program. There's more to RTTI than a simple cast. For example, there's a way to see what type you're dealing with before you try to downcast it. All of Chapter 11 is devoted to the study of different aspects of Java run-time type identification. Summary Polymorphism means different forms. In object-oriented programming, you have the same face (the common interface in the base class) and different forms using that face: the different versions of the dynamically-bound methods. You've seen in this chapter that it's impossible to understand, or even create, an example of polymorphism without using data abstraction and inheritance. Polymorphism is a feature that cannot be viewed in isolation (like a switch statement, for example), but instead works only in concert, as part of a big picture of class relationships. People are often confused by other, non-object-oriented features of Java, like method overloading, which are sometimes presented as object-oriented. Don't be fooled: If it isn't late binding, it isn't polymorphism. Although this requires significant effort, it's a worthy struggle, because the results are faster program development, better code organization, extensible programs, and easier code maintenance. Exercises 1. Create an inheritance hierarchy of Rodent: Mouse, Gerbil, Hamster, etc. In the base class, provide methods that are common to all Rodents, and override these in the derived classes to perform different behaviors depending on the specific type of Rodent. Create an array of Rodent, fill it with different specific types of Rodents, and call your base-class methods to see what happens. 2. Change Exercise 1 so that Rodent is an interface. 3. Repair the problem in WindError.java. 4. In GreenhouseControls.java, add Event inner classes that turn fans on and off. In general, your programs will always be creating new objects based on some criteria that will be known only at the time the program is running. You won't know until run-time the quantity or even the exact type of the objects you need. To solve the general programming problem, you need to create any number of objects, anytime, anywhere. So you can't rely on creating a named handle to hold each one of your objects: MyObject myHandle; since you'll never know how many of these things you'll actually need. To solve this rather essential problem, Java has several ways to hold objects (or rather, handles to objects). The built-in type is the array, which has been discussed before and will get additional coverage in this chapter. Also, the Java utilities library has some collection classes (also known as container classes, but that term is used by the AWT so collection will be used here) that provide more sophisticated ways to hold and even manipulate your objects. This will comprise the remainder of this chapter. Arrays Most of the necessary introduction to arrays is in the last section of Chapter 4, which shows how you define and initialize an array. Holding objects is the focus of this chapter, and an array is just one way to hold objects. But there are a number of other ways to hold objects, so what makes an array special? There are two issues that distinguish arrays from other types of collections: efficiency and type. The array is the most efficient way that Java provides to store and access a sequence of objects (actually, object handles). The array is a simple linear sequence, which makes element access fast, but you pay for this speed: when you create an array object, its size is fixed and cannot be changed for the lifetime of that array object. You might suggest creating an array of a particular size and then, if you run out of space, creating a new one and moving all the handles from the old one to the new one. This is the behavior of the Vector class, which will be studied later in the chapter. However, because of the overhead of this size flexibility, a Vector is measurably less efficient than an array. As you'll learn in Chapter 9, this type of exception indicates a programmer error and thus you don't need to check for it in your code. As an aside, the reason the C++ vector doesn't check bounds with every access is speed - in Java you have the constant performance overhead of bounds checking all the time for both arrays and collections. The other generic collection classes that will be studied in this chapter, Vector, Stack, and Hashtable, all deal with objects as if they had no specific type. That is, they treat them as type Object, the root class of all classes in Java. This works fine from one standpoint: you need to build only one collection, and any Java object will go into that collection. This means that you get compile-time type checking to prevent you from putting the wrong type in, or mistaking the type that you're extracting. Of course, Java will prevent you from sending an inappropriate message to an object, either at compile-time or at run-time. So it's not as if it's riskier one way or the other, it's just nicer if the compiler points it out to you, faster at run-time, and there's less likelihood that the end user will get surprised by an exception. For efficiency and type checking it's always worth trying to use an array if you can. However, when you're trying to solve a more general problem arrays can be too restrictive. After looking at arrays, the rest of this chapter will be devoted to the collection classes provided by Java. Arrays are first-class objects Regardless of what type of array you're working with, the array identifier is actually a handle to a true object that's created on the heap. The heap object can be created either implicitly, as part of the array initialization syntax, or explicitly with a new expression. Part of the heap object (in fact, the only field or method you can access) is the read-only length member that tells you how many elements can be stored in that array object. The following example shows the various ways that an array can be initialized, and how the array handles can be assigned to different array objects. It also shows that arrays of objects and arrays of primitives are almost identical in their use. The only difference is that arrays of objects hold handles while arrays of primitives hold the primitive values directly. The array b is initialized to point to an array of Weeble handles, but no actual Weeble objects are ever placed in that array. However, you can still ask what the size of the array is, since b is pointing to a legitimate object. However, when an array object is created its handles are automatically initialized to null so you can see whether a particular array slot has an object in it by checking to see whether it's null. Similarly, an array of primitives is automatically initialized to zero for numeric types, null for char, and false for boolean. Array c shows the creation of the array object followed by the assignment of Weeble objects to all the slots in the array. Array d shows the aggregate initialization syntax that causes the array object to be created (implicitly with new on the heap, just like for array c) and initialized with Weeble objects, all in one statement. The expression a = d; shows how you can take a handle that's attached to one array object and assign it to another array object, just as you can do with any other type of object handle. Now both a and d are pointing to the same array object on the heap. For example, suppose hide( ) is a method that takes an array of Weeble objects. The second part of the above example shows that primitive arrays work just like object arrays except that primitive arrays hold the primitive values directly. Collections of primitives Collection classes can hold only handles to objects. An array, however, can be created to hold primitives directly, as well as handles to objects. It is possible to use the wrapper classes such as Integer, Double, etc. Whether you put primitives in arrays or wrap them in a class that's placed in a collection is a question of efficiency. It's much more efficient to create and access an array of primitives than a collection of wrapped primitives. Of course, if you're using a primitive type and you need the flexibility of a collection that automatically expands when more space is needed, the array won't work and you're forced to use a collection of wrapped primitives. You might think that there should be a specialized type of Vector for each of the primitive data types, but Java doesn't provide this for you. Some sort of templatizing mechanism might someday provide a better way for Java to handle this problem.1 Returning an array Suppose you're writing a method and you don't just want to return one thing, but a whole bunch of things. Languages like C and C++ make this difficult because you can't just return an array, only a pointer to an array. This introduces problems because it becomes messy to control the lifetime of the array, which easily leads to memory leaks. The size of this array is n, determined by the argument you pass into the method. Then it proceeds to choose flavors randomly from the array flav and place them into results, which it finally returns. Returning an array is just like returning any other object - it's a handle. It's not important that the array was created within flavorSet( ), or that the array was created anyplace else, for that matter. The garbage collector takes care of cleaning up the array when you're done with it, and the array will persist for as long as you need it. As an aside, notice that when flavorSet( ) chooses flavors randomly, it ensures that a random choice hasn't been picked before. This is performed in a seemingly infinite while loop that keeps making random choices until it finds one that's not already in the picks array. But if t is a number that's already in picks, then a labeled continue is used to jump back two levels, which forces a new t to be selected. It's particularly convincing to watch this happen with a debugger. It's easiest to see this if you redirect the output into a file. And while you're looking at the file, remember, you're not really hungry. In the remainder of the chapter we'll look at the more general case, when you don't know at the time you're writing the program how many objects you're going to need, or if you need a more sophisticated way to store your objects. Java provides four types of collection classes to solve this problem: Vector, BitSet, Stack, and Hashtable. Although compared to other languages that provide collections this is a fairly meager supply, you can nonetheless solve a surprising number of problems using these tools. Thus, you can put in any number of objects and you don't need to worry about how big to make the collection while you're writing the program. Disadvantage: unknown type The disadvantage to using the Java collections is that you lose type information when you put an object into a collection. So instead, the collection holds handles to objects of type Object, which is of course every object in Java, since it's the root of all the classes. Someone could just as easily put a dog into the collection. 2. Since the type information is lost, the only thing the collection knows it holds is a handle to an Object. You must perform a cast to the correct type before you use it. On the up side, Java won't let you misuse the objects that you put into a collection. If you throw a dog into a collection of cats, then go through and try to treat everything in the collection as a cat, you'll get an exception when you get to the dog. In the same vein, if you try to cast the dog handle that you pull out of the cat collection into a cat, you'll get an exception at run-time. When you go to fetch out what you think are Cat objects using the Vector method elementAt( ), you get back a handle to an Object that you must cast to a Cat. Then you need to surround the entire expression with parentheses to force the evaluation of the cast before calling the print( ) method for Cat, otherwise you'll get a syntax error. Then, at run-time, when you try to cast the Dog object to a Cat, you'll get an exception. This is more than just an annoyance. It's something that can create some difficult-to-find bugs. You do this by code inspection, which is about the worst debugging tool you have. On the upside, it's convenient to start with some standardized collection classes for programming, despite the scarcity and awkwardness. Sometimes it works right anyway It turns out that in some cases things seem to work correctly without casting back to your original type. The first case is quite special: the String class has some extra help from the compiler to make it work smoothly. Whenever the compiler expects a String object and it hasn't got one, it will automatically call the toString( ) method that's defined in Object and can be overridden by any Java class. This method produces the desired String object, which is then used wherever it was wanted. In the second for loop in main( ) you find the statement: System.out.println(Free mouse: + mice.elementAt(i)); After the '+' sign the compiler expects to see a String object. Unfortunately, you can work this kind of magic only with String; it isn't available for any other type. A second approach to hiding the cast has been placed inside Mousetrap. The caughtYa( ) method accepts not a Mouse, but an Object, which it then casts to a Mouse. This is quite presumptuous, of course, since by accepting an Object anything could be passed to the method. However, if the cast is incorrect - if you passed the wrong type - you'll get an exception at run-time. This is not as good as compile-time checking but it's still robust. Note that in the use of this method: MouseTrap.caughtYa(mice.elementAt(i)); no cast is necessary. Making a type-conscious Vector You might not want to give up on this issue just yet. However, it doesn't accept and produce generic Objects, only Gopher objects. Because a GopherVector will accept only a Gopher, if you were to say: gophers.addElement(new Pigeon()); you would get an error message at compile time. This approach, while more tedious from a coding standpoint, will tell you immediately if you're using a type improperly. Note that no cast is necessary when using elementAt( ) - it's always a Gopher. Parameterized types This kind of problem isn't isolated - there are numerous cases in which you need to create new types based on other types, and in which it is useful to have specific type information at compile-time. This is the concept of a parameterized type. In C++, this is directly supported by the language in templates. At one point, Java had reserved the keyword generic to someday support parameterized types, but it's uncertain if this will ever occur. Enumerators (iterators) In any collection class, you must have a way to put things in and a way to get things out. After all, that's the primary job of a collection - to hold things. In the Vector, addElement( ) is the way that you insert objects, and elementAt( ) is one way to get things out. Vector is quite flexible - you can select anything at any time, and select multiple elements at once using different indexes. If you want to start thinking at a higher level, there's a drawback: you need to know the exact type of the collection in order to use it. This might not seem bad at first, but what if you start out using a Vector, and later on in your program you decide, for efficiency, that you want to change to a List (which is part of the Java 1.2 collections library)? Or you'd like to write a piece of code that doesn't know or care what type of collection it's working with. The concept of an iterator can be used to achieve this next level of abstraction. This is an object whose job is to move through a sequence of objects and select each object in that sequence without the client programmer knowing or caring about the underlying structure of that sequence. In addition, an iterator is usually what's called a light-weight object; that is, one that's cheap to create. For that reason, you'll often find seemingly strange constraints for iterators; for example, some iterators can move in only one direction. The Java Enumeration2 is an example of an iterator with these kinds of constraints. There's not much you can do with one except: 1. Ask a collection to hand you an Enumeration using a method called elements( ). This Enumeration will be ready to return the first element in the sequence on your first call to its nextElement( ) method. 2. Get the next object in the sequence with nextElement( ). 3. See if there are any more objects in the sequence with hasMoreElements( ). That's all. It's a simple implementation of an iterator, but still powerful. To see how it works, let's revisit the CatsAndDogs.java program from earlier in the chapter. That's taken care of for you by hasMoreElements( ) and nextElement( ). All you have is an Enumeration, and that's all you need to know about the sequence: that you can get the next object, and that you can know when you're at the end. This idea of taking a collection of objects and passing through it to perform an operation on each one is powerful and will be seen throughout this book. This particular example is even more generic, since it uses the ubiquitous toString( ) method (ubiquitous only because it's part of the Object class). An alternative way to call print (although probably slightly less efficient, if you could even notice the difference) is: System.out.println( + e.nextElement()); which uses the automatic conversion to String that's wired into Java. When the compiler sees a String, followed by a '+', it expects another String to follow and calls toString( ) automatically. You must assume you've gotten an Enumeration to a sequence of the particular type you're interested in, and cast the resulting objects to that type (getting a run-time exception if you're wrong). Types of collections The standard Java 1.0 and 1.1 library comes with a bare minimum set of collection classes, but they're probably enough to get by with for many of your programming projects. Although most of the time you'll just use addElement( ) to insert objects, elementAt( ) to get them out one at a time, and elements( ) to get an Enumeration to the sequence, there's also a set of other methods that can be useful. As usual with the Java libraries, we won't use or talk about them all here, but be sure to look them up in the electronic documentation to get a feel for what they can do. Crashing Java The Java standard collections contain a toString( ) method so they can produce a String representation of themselves, including the objects they hold. Inside of Vector, for example, the toString( ) steps through the elements of the Vector and calls toString( ) for each one. Suppose you'd like to print out the address of your class. However, if you place the CrashJava objects in a Vector and print out that Vector as shown here, it can't handle it and you don't even get an exception; Java just crashes. When you say: CrashJava address: + this The compiler sees a String followed by a '+' and something that's not a String, so it tries to convert this to a String. It does this conversion by calling toString( ), which produces a recursive call. When this occurs inside a Vector, it appears that the stack overflows without the exception-handling mechanism getting a chance to respond. If you really do want to print the address of the object in this case, the solution is to call the Object toString( ) method, which does just that. So instead of saying this, you'd say super.toString( ). BitSet A BitSet is really a Vector of bits, and it is used if you want to efficiently store a lot of on-off information. It's efficient only from the standpoint of size; if you're looking for efficient access, it is slightly slower than using an array of some native type. In addition, the minimum size of the BitSet is that of a long: 64 bits. This implies that if you're storing anything smaller, like 8 bits, a BitSet will be wasteful, so you're better off creating your own class to hold your flags. In a normal Vector, the collection will expand as you add more elements. The BitSet does this as well - sort of. That is, sometimes it works and sometimes it doesn't, which makes it appear that the Java version 1.0 implementation of BitSet is just badly done. This works fine because a BitSet is 64 bits, so none of these cause it to increase in size. But in Java 1.0, when the BitSet is greater than 64 bits, some strange behavior occurs. If you set a bit that's just one greater than the BitSet's currently-allocated storage, it will expand nicely. But if you try to set bits at higher locations than that without first just touching the boundary, you'll get an exception, since the BitSet won't expand properly in Java 1.0. The example shows a BitSet of 512 bits being created. The constructor allocates storage for twice that number of bits. That is, whatever you push on the Stack last is the first item you can pop out. Like all of the other collections in Java, what you push and pop are Objects, so you must cast what you pop. What's rather odd is that instead of using a Vector as a building block to create a Stack, Stack is inherited from Vector. So it has all of the characteristics and behaviors of a Vector plus some extra Stack behaviors. It's difficult to know whether the designers explicitly decided that this was an especially useful way to do things, or whether it was just a na‹ve design. To make a point, Vector operations are also performed on the Stack object. This is possible because, by virtue of inheritance, a Stack is a Vector. Thus, all operations that can be performed on a Vector can also be performed on a Stack, such as elementAt( ). Hashtable A Vector allows you to select from a sequence of objects using a number, so in a sense it associates numbers to objects. But what if you'd like to select from a sequence of objects using some other criterion? A Stack is an example: its selection criterion is the last thing pushed on the stack. A powerful twist on this idea of selecting from a sequence is alternately termed a map, a dictionary, or an associative array. Conceptually, it seems like a vector, but instead of looking up objects using a number, you look them up using another object! This is often a key process in a program. The concept shows up in Java as the abstract class Dictionary. There are enumerations: keys( ) produces an Enumeration of the keys, and elements( ) produces an Enumeration of all the values. That's all there is to a Dictionary. A Dictionary isn't terribly difficult to implement. This means that AssocArray is a type of Dictionary, so you can make the same requests of it that you can a Dictionary. If you make your own Dictionary, as is done here, all you need to do is fill in all the methods that are in Dictionary. And if you look at get( ), when you pass roof in as the key, it produces the index number with keys.indexOf( ), and then uses that index number to produce the value in the associated values vector. The test in main( ) is simple; it's just a map of lowercase characters to uppercase characters, which could obviously be done in a number of more efficient ways. But it shows that AssocArray is functional. The standard Java library contains only one embodiment of a Dictionary, called Hashtable.3 Java's Hashtable has the same basic interface as AssocArray (since they both inherit Dictionary), but it differs in one distinct way: efficiency. If you look at what must be done for a get( ), it seems pretty slow to search through a Vector for the key. This is where Hashtable speeds things up. Instead of the tedious linear search for the key, it uses a special value called a hash code. The hash code is a way to take some information in the object in question and turn it into a relatively unique int for that object. All objects have a hash code, and hashCode( ) is a method in the root class Object. A Hashtable takes the hashCode( ) of the object and uses it to quickly hunt for the key. This results in a dramatic performance improvement.4 The way that a Hashtable works is beyond the scope of this book5 - all you need to know is that Hashtable is a fast Dictionary, and that a Dictionary is a useful tool. As an example of the use of a Hashtable, consider a program to check the randomness of Java's Math.random( ) method. Ideally, it would produce a perfect distribution of random numbers, but to test this you need to generate a bunch of random numbers and count the ones that fall in the various ranges. The value i inside the counter is then incremented to indicate that one more of this particular random number has been found. If the key has not been found yet, the method put( ) will place a new key-value pair into the Hashtable. Since Counter automatically initializes its variable i to one when it's created, it indicates the first occurrence of this particular random number. To display the Hashtable, it is simply printed out. The Hashtable toString( ) method moves through all the key-value pairs and calls the toString( ) for each one. The Integer toString( ) is pre-defined, and you can see the toString( ) for Counter. Why not use int or Integer? Well, you can't use an int because all of the collections can hold only Object handles. After seeing collections the wrapper classes might begin to make a little more sense to you, since you can't put any of the primitive types in collections. However, the only thing you can do with the Java wrappers is to initialize them to a particular value and read that value. That is, there's no way to change a value once a wrapper object has been created. This makes the Integer wrapper immediately useless to solve our problem, so we're forced to create a new class that does satisfy the need. Creating key classes In the previous example, a standard library class (Integer) was used as a key for the Hashtable. It worked fine as a key, because it has all the necessary wiring to make it work correctly as a key. But a common pitfall occurs when using Hashtables when you create your own classes to be used as keys. For example, consider a weather predicting system that matches Groundhog objects to Prediction objects. In main( ), a Hashtable is filled with Groundhogs and their associated Predictions. The Hashtable is printed so you can see that it has been filled. Then a Groundhog with an identity number of 3 is used to look up the prediction for Groundhog #3. It seems simple enough, but it doesn't work. The problem is that Groundhog is inherited from the common root class Object (which is what happens if you don't specify a base class, thus all classes are ultimately inherited from Object). It is Object's hashCode( ) method that is used to generate the hash code for each object, and by default it just uses the address of its object. Thus, the first instance of Groundhog(3) does not produce a hash code equal to the hash code for the second instance of Groundhog(3) that we tried to use as a lookup. You might think that all you need to do is write an appropriate override for hashCode( ). But it still won't work until you've done one more thing: override the equals( ) that is also part of Object. This method is used by the Hashtable when trying to determine if your key is equal to any of the keys in the table. Again, the default Object.equals( ) simply compares object addresses, so one Groundhog(3) is not equal to another Groundhog(3). Groundhog2.hashCode( ) returns the ground hog number as an identifier. The equals( ) method does two sanity checks: to see if the object is null, and if not, whether it is an instance of Groundhog2 (using the instanceof keyword, which is fully explained in Chapter 11). It should be a Groundhog2 to even continue executing equals( ). The comparison, as you can see, is based on the actual ghNumbers. This time, when you run the program, you'll see it produces the correct output. In that example, the lines: Properties p = System.getProperties(); p.list(System.out); called the static method getProperties( ) to get a special Properties object that described the system characteristics. The method list( ) is a method of Properties that sends the contents to any stream output that you choose. There's also a save( ) method to allow you to write your property list to a file in a way that it can be retrieved later with the load( ) method. Although the Properties class is inherited from Hashtable, it also contains a second Hashtable that acts to hold the list of default properties. So if a property isn't found in the primary list, the defaults will be searched. The Properties class is also available for use in your programs (an example is ClassScanner.java in Chapter 17). You can find more complete details in the Java library documentation. Enumerators revisited We can now demonstrate the true power of the Enumeration: the ability to separate the operation of traversing a sequence from the underlying structure of that sequence. In the following example, the class PrintData uses an Enumeration to move through a sequence and call the toString( ) method for every object. Two different types of collections are created, a Vector and a Hashtable, and they are each filled with, respectively, Mouse and Hamster objects. It's more likely that in your problem, you must make the assumption that your Enumeration is walking through a collection of some specific type. For example, you might assume that everything in the collection is a Shape with a draw( ) method. Then you must downcast from the Object that Enumeration.nextElement() returns to produce a Shape. Sorting One of the things missing in the Java 1.0 and 1.1 libraries is algorithmic operations, even simple sorting. So it makes sense to create a Vector that sorts itself using the classic Quicksort. A problem with writing generic sorting code is that sorting must perform comparisons based on the actual type of the object. Of course, one approach is to write a different sorting method for every different type, but you should be able to recognize that this does not produce code that is easily re-used for new types. So instead of hard-wiring the comparison code into many different sort routines, the technique of the callback will be used. With a callback, the part of the code that varies from case to case is encapsulated inside its own class, and the part of the code that's always the same will call back to the code that changes. That way you can make different objects to express different ways of comparison and feed them to the same sorting code. A subclass of Vector can be created that implements the Quicksort using Compare. The algorithm, which is known for its speed, will not be explained here. For details, see Practical Algorithms for Programmers, by Binstock and Rex, Addison-Wesley 1995. You can also see how this technique has produced generic, reusable code. To use the SortVector, you must create a class that implements Compare for the kind of objects that you're sorting. This is a place where an inner class is not essential, but it can make sense for code organization. You can see how, once the framework is set up, it's easy to reuse a design like this - you simply write the class that encapsulates the things that change and hand an object to the SortVector. The comparison forces the strings to lower case, so that the capital A's end up next to the small a's and not in some entirely different place. This example shows, however, a slight deficiency in this approach, since the test code above puts the uppercase and lowercase single letters of the same letter in the order that they appear: A a b B c C d D. This is not usually much of a problem, because you're usually working with longer strings and in that situation the effect doesn't show up. The use of inheritance here is powerful but it presents problems. It turns out that some methods are final (described in Chapter 7), so you cannot override them. No luck there. On the other hand, consider composition: the placing of an object inside a new class. Rather than rewrite the above code to accomplish this, we can simply use a SortVector inside the new class. However, not all of the public methods from SortVector and Vector appear in StrSortVector. When reusing code this way, you can make a definition in the new class for each one in the contained class, or you can start with just a few and periodically go back and add more when you need them. Eventually the new class design will settle down. The advantage to this approach is that it will take only String objects and produce only String objects, and the checking happens at compile time instead of run time. Of course, that's only true for addElement( ) and elementAt( ); elements( ) still produces an Enumeration that is untyped at compile time. Type checking for the Enumeration and in StrSortVector still happens, of course, it just happens at run-time by throwing exceptions if you do something wrong. It's a trade-off: do you find out about something for sure at compile time or probably at run-time? You can see there's a flag called sorted in this class. You could sort the vector every time addElement( ) is called, and constantly keep it in a sorted state. But usually people add a lot of elements to a Vector before beginning to read it. So sorting after every addElement( ) would be less efficient than waiting until someone wants to read the vector and then sorting it, which is what is done here. The technique of delaying a process until it is absolutely necessary is called lazy evaluation. In addition, algorithms like sorting are not supported at all. One of the strengths of C++ is its libraries, in particular the Standard Template Library (STL) that provides a fairly full set of collections as well as many algorithms like sorting and searching that work with those collections. The JGL seems to fulfill many, if not all, of the needs for a collection library, or as far as one could go in this direction without C++'s template mechanism. The JGL includes linked lists, sets, queues, maps, stacks, sequences, and iterators that are far more functional than Enumeration, as well as a full set of algorithms such as searching and sorting. ObjectSpace also made, in some cases, more intelligent design decisions than the Sun library designers. For example, the methods in the JGL collections are not final so it's easy to inherit and override those methods. The online documentation that comes in the JGL package is quite good and should be adequate to get you started. The new collections To me, collection classes are one of the most powerful tools for raw programming. Some of the redesign makes things tighter and more sensible. For example, many names are shorter, cleaner, and easier to understand, as well as to type. Some names are changed to conform to accepted terminology: a particular favorite of mine is iterator instead of enumeration. The redesign also fills out the functionality of the collections library. You can now have the behavior of linked lists, queues, and dequeues (double-ended queues, pronounced decks). The design of a collections library is difficult (true of most library design problems). In C++, the STL covered the bases with many different classes. This was better than what was available prior to the STL (nothing), but it didn't translate well into Java. The result was a rather confusing morass of classes. On the other extreme, I've seen a collections library that consists of a single class, collection, which acts like a Vector and a Hashtable at the same time. The designers of the new collections library wanted to strike a balance: the full functionality that you expect from a mature collections library, but easier to learn and use than the STL and other similar collections libraries. The result can seem a bit odd in places. Unlike some of the decisions made in the early Java libraries, these oddities were not accidents, but carefully considered decisions based on tradeoffs in complexity. It might take you a little while to get comfortable with some aspects of the library, but I think you'll find yourself rapidly acquiring and using these new tools. The new collections library takes the issue of holding your objects and divides it into two distinct concepts: 1. Collection: a group of individual elements, often with some rule applied to them. A List must hold the elements in a particular sequence, and a Set cannot have any duplicate elements. At first glance, this might seem like it ought to be a Collection of pairs, but when you try to implement it that way the design gets awkward, so it's clearer to make it a separate concept. On the other hand, it's convenient to look at portions of a Map by creating a Collection to represent that portion. Thus, a Map can return a Set of its keys, a List of its values, or a List of its pairs. When you see this, the new collections should not seem so daunting. The dashed boxes represent interfaces, the dotted boxes represent abstract classes, and the solid boxes are regular (concrete) classes. The dashed arrows indicate that a particular class is implementing an interface (or in the case of an abstract class, partially implementing that interface). The double-line arrows show that a class can produce objects of the class the arrow is pointing to. For example, any Collection can produce an Iterator, while a List can produce a ListIterator (as well as an ordinary Iterator, since List is inherited from Collection). The interfaces that are concerned with holding objects are Collection, List, Set, and Map. Typically, you'll write the bulk of your code to talk to these interfaces, and the only place where you'll specify the precise type you're using is at the point of creation. So you can create a List like this: List x = new LinkedList(); Of course, you can also decide to make x a LinkedList (instead of a generic List) and carry the precise type information around with x. In the class hierarchy, you can see a number of classes whose names begin with Abstract, and these can seem a bit confusing at first. They are simply tools that partially implement a particular interface. If you were making your own Set, for example, you wouldn't start with the Set interface and implement all the methods, instead you'd inherit from AbstractSet and do the minimal necessary work to make your new class. However, the new collections library contains enough functionality to satisfy your needs virtually all the time. You'll typically make an object of a concrete class, upcast it to the corresponding interface, and then use the interface throughout the rest of your code. You can see that the new collections are part of the java.util library, so you don't need to add any extra import statements to use them. The first line in main( ) creates an ArrayList object and then upcasts it to a Collection. Since this example uses only the Collection methods, any object of a class inherited from Collection would work, but ArrayList is the typical workhorse Collection and takes the place of Vector. The add( ) method, as its name suggests, puts a new element in the Collection. However, the documentation carefully states that add( ) ensures that this Collection contains the specified element. This is to allow for the meaning of Set, which adds the element only if it isn't already there. With an ArrayList, or any sort of List, add( ) always means put it in. All Collections can produce an Iterator via their iterator( ) method. An Iterator is just like an Enumeration, which it replaces, except: 1. It uses a name (iterator) that is historically understood and accepted in the OOP community. 2. It uses shorter method names than Enumeration: hasNext( ) instead of hasMoreElements( ), and next( ) instead of nextElement( ). 3. It adds a new method, remove( ), which removes the last element produced by the Iterator. So you can call remove( ) only once for every time you call next( ). In SimpleCollection.java, you can see that an Iterator is created and used to traverse the Collection, printing each element. Using Collections The following table shows everything you can do with a Collection, and thus, everything you can do with a Set or a List. Returns false if it doesn't add the argument. Returns true if any elements were added. Iterator iterator( ) Returns an Iterator that you can use to move through the elements in the Collection. Returns true if a removal occurred. Returns true if any removals occurred. Returns true if any changes occurred. If not, that method throws an UnsupportedOperationException. Exceptions will be covered in Chapter 9. The following example demonstrates all of these methods. The second method will be used frequently throughout the rest of this chapter. The two versions of newCollection( ) create ArrayLists containing different sets of data and return them as Collection objects, so it's clear that nothing other than the Collection interface is being used. The print( ) method will also be used throughout the rest of this section. Since it moves through a Collection using an Iterator, which any Collection can produce, it will work with Lists and Sets and any Collection that a Map produces. The following sections compare the various implementations of List, Set, and Map and indicate in each case (with an asterisk) which one should be your default choice. You'll notice that the legacy classes Vector, Stack, and Hashtable are not included because in all cases there are preferred classes within the new collections. Using Lists List (interface) Order is the most important feature of a List; it promises to maintain elements in a particular sequence. List adds a number of methods to Collection that allow insertion and removal of elements in the middle of a List. ArrayList* A List backed by an array. Use instead of Vector as a general-purpose object holder. Allows rapid random access to elements, but is slow when inserting and removing elements from the middle of a list. ListIterator should be used only for back-and-forth traversal of an ArrayList, but not for inserting and removing elements, which is expensive compared to LinkedList. LinkedList Provides optimal sequential access, with inexpensive insertions and deletions from the middle of the list. Relatively slow for random access. In some cases, the return value isn't captured since it isn't typically used. You should look up the full usage of each of these methods in your online documentation before you use them. Using Sets Set has exactly the same interface as Collection, so there isn't any extra functionality as there is with the two different Lists. Instead, the Set is exactly a Collection, it just has different behavior. Set (interface) Each element that you add to the Set must be unique; otherwise the Set doesn't add the duplicate element. Objects added to a Set must define equals( ) to establish object uniqueness. Set has exactly the same interface as Collection. A Set does not guarantee it will maintain its elements in any particular order. HashSet* For all Sets except very small ones. Objects must also define hashCode( ). ArraySet A Set backed by an array. Designed for very small Sets, especially those that are frequently created and destroyed. For small Sets, creation and iteration is substantially cheaper than for HashSet. Performance gets quite bad when the Set is large. HashCode( ) is not required. TreeSet An ordered Set backed by a red-black tree.7 This way, you can extract an ordered sequence from a Set. The following example does not show everything you can do with a Set, since the interface is the same as Collection and so was exercised in the previous example. When you run this program you'll notice that the order maintained by the HashSet is different from ArraySet, since each has a different way of storing elements so they can be located later. You must define an equals( ) in both cases, but the hashCode( ) is necessary only if the class will be placed in a HashSet (which is likely, since that should generally be your first choice as a Set implementation). Using Maps Map (interface) Maintains key-value associations (pairs), so you can look up a value using a key. HashMap* Implementation based on a hash table. Performance can be adjusted via constructors that allow you to set the capacity and load factor of the hash table. ArrayMap Map backed by an ArrayList. Gives precise control over the order of iteration. Designed for very small Maps, especially those that are frequently created and destroyed. For very small Maps, creation and iteration is substantially cheaper than for HashMap. Performance gets very bad when the Map is large. TreeMap Implementation based on a red-black tree. When you view the keys or the pairs, they will be in sorted order (determined by Comparable or Comparator, discussed later). The point of a TreeMap is that you get the results in sorted order. TreeMap is the only Map with the subMap( ) method, which allows you to return a portion of the tree. The following example contains two sets of test data and a fill( ) method that allows you to fill any map with any two-dimensional array of Objects. These tools will be used in other Map examples, as well. The keySet( ) method produces a Set backed by the keys in the Map; here, it is treated as only a Collection. Similar treatment is given to values( ), which produces a List containing all the values in the Map. The print( ) method grabs the Iterator produced by entries and uses it to print both the key and value for each pair. The rest of the program provides simple examples of each Map operation, and tests each type of Map. When creating your own class to use as a key in a Map, you must deal with the same issues discussed previously for Sets. Choosing an implementation From the diagram on page 363 you can see that there are really only three collection components: Map, List, and Set, and only two or three implementations of each interface. If you need to use the functionality offered by a particular interface, how do you decide which particular implementation to use? To understand the answer, you must be aware that each different implementation has its own features, strengths, and weaknesses. For example, you can see in the diagram that the feature of Hashtable, Vector, and Stack is that they are legacy classes, so that existing code doesn't break. On the other hand, it's best if you don't use those for new (Java 1.2) code. The distinction between the other collections often comes down to what they are backed by; that is, the data structures that physically implement your desired interface. This means that, for example, ArrayList, LinkedList, and Vector (which is roughly equivalent to ArrayList) all implement the List interface so your program will produce the same results regardless of the one you use. Because of this, if you want to do many insertions and removals in the middle of a list a LinkedList is the appropriate choice. As another example, a Set can be implemented as either an ArraySet or a HashSet. An ArraySet is backed by an ArrayList and is designed to support only small numbers of elements, especially in situations in which you're creating and destroying a lot of Set objects. However, if you're going to have larger quantities in your Set, the performance of ArraySet will get very bad, very quickly. When you're writing a program that needs a Set, you should choose HashSet by default, and change to ArraySet only in special cases where performance improvements are indicated and necessary. Choosing between Lists The most convincing way to see the differences between the implementations of List is with a performance test. The following code establishes an inner base class to use as a test framework, then creates an anonymous inner class for each different test. Each of these inner classes is called by the test( ) method. This approach allows you to easily add and remove new kinds of tests. It contains a String to be printed when the test starts, a size parameter to be used by the test for quantity of elements or repetitions of tests, a constructor to initialize the fields, and an abstract method test( ) that does the work. All the different types of tests are collected in one place, the array tests, which is initialized with different anonymous inner classes that inherit from Tester. To add or remove tests, simply add or remove an inner class definition from the array, and everything else happens automatically. The List that's handed to test( ) is first filled with elements, then each test in the tests array is timed. The results will vary from machine to machine; they are intended to give only an order of magnitude comparison between the performance of the different collections. Here is a summary of one run: Type Get Iteration Insert Remove ArrayList 110 270 1920 4780 LinkedList 1870 7580 170 110 You can see that random accesses (get( )) and iterations are cheap for ArrayLists and expensive for LinkedLists. On the other hand, insertions and removals from the middle of a list are significantly cheaper for a LinkedList than for an ArrayList. The best approach is probably to choose an ArrayList as your default and to change to a LinkedList if you discover performance problems because of many insertions and removals from the middle of the list. Choosing between Sets You can choose between an ArraySet and a HashSet, depending on the size of the Set (if you need to produce an ordered sequence from a Set, use TreeSet8). You'll virtually never want to use an ArraySet for regular programming. Here is one set of results. Why would you use a TreeMap if it has good put( ) and iteration times? So you could use it not as a Map, but as a way to create an ordered list. The behavior of a tree is such that it's always in order and doesn't have to be specially sorted. You can then use the static method Array.binarySearch( ) (discussed later) to rapidly find objects in your sorted array. Of course, you would probably only do this if, for some reason, the behavior of a HashMap was unacceptable, since HashMap is designed to rapidly find things. In the end, when you're using a Map your first choice should be HashMap, and only rarely will you need to investigate the alternatives. There is another performance issue that the above table does not address, and that is speed of creation. Again, you should only worry about this sort of thing after it's been proven that you have a performance bottleneck. The rest of the methods cause the unwelcome appearance of something called an UnsupportedOperationException. Calling an unsupported method causes an UnsupportedOperationException to indicate a programming error. What?!? you say, incredulous. The whole point of interfaces and base classes is that they promise these methods will do something meaningful! This breaks that promise - it says that not only will calling some methods not perform a meaningful behavior, they will stop the program! Type safety was just thrown out the window! It's not quite that bad. In addition, most methods that take a Collection as an argument only read from that Collection -all the read methods of Collection are not optional. This approach prevents an explosion of interfaces in the design. Other designs for collection libraries always seem to end up with a confusing plethora of interfaces to describe each of the variations on the main theme and are thus difficult to learn. It's not even possible to capture all of the special cases in interfaces, because someone can always invent a new interface. The unsupported operation approach achieves an important goal of the new collections library: it is simple to learn and use. For this approach to work, however: 1. The UnsupportedOperationException must be a rare event. That is, for most classes all operations should work, and only in special cases should an operation be unsupported. This is true in the new collections library, since the classes you'll use 99 percent of the time - ArrayList, LinkedList, HashSet, and HashMap, as well as the other concrete implementations - support all of the operations. The design does provide a back door if you want to create a new Collection without providing meaningful definitions for all the methods in the Collection interface, and yet still fit it into the existing library. 2. When an operation is unsupported, there should be reasonable likelihood that an UnsupportedOperationException will appear at implementation time, rather than after you've shipped the product to the customer. After all, it indicates a programming error: you've used a class incorrectly. This point is less certain, and is where the experimental nature of this design comes into play. Only over time will we find out how well it works. In the example above, Arrays.toList( ) produces a List that is backed by a fixed-size array. Therefore it makes sense that the only supported operations are the ones that don't change the size of the array. The documentation for a method that takes a Collection, List, Set, or Map as an argument should specify which of the optional methods must be implemented. For example, sorting requires the set( ) and Iterator.set( ) methods but not add( ) and remove( ). Sorting and searching Java 1.2 adds utilities to perform sorting and searching for arrays or Lists. These utilities are static methods of two new classes: Arrays for sorting and searching arrays, and Collections for sorting and searching Lists. Arrays The Arrays class has an overloaded sort( ) and binarySearch( ) for arrays of all the primitive types, as well as for String and Object. The two print( ) methods simplify the display of the sample arrays. In main( ), Random.nextBytes( ) fills the array argument with randomly-selected bytes. There's an important warning concerning binarySearch( ): If you do not call sort( ) before you perform a binarySearch( ), unpredictable behavior can occur, including infinite loops. Sorting and searching with Strings looks the same, but when you run the program you'll notice something interesting: the sorting is lexicographic, so uppercase letters precede lowercase letters in the character set. Thus, all the capital letters are at the beginning of the list, followed by the lowercase letters, so 'Z' precedes 'a'. It turns out that even telephone books are typically sorted this way. Comparable and Comparator What if this isn't what you want? For example, the index in this book would not be too useful if you had to look in two places for everything that begins with 'A' or 'a'. When you want to sort an array of Object, there's a problem. What determines the ordering of two Objects? Unfortunately, the original Java designers didn't consider this an important problem, or it would have been defined in the root class Object. As a result, ordering must be imposed on Objects from the outside, and the new collections library provides a standard way to do this (which is almost as good as defining it in Object). This method takes the two objects to be compared as its arguments and returns a negative integer if the first argument is less than the second, zero if they're equal, and a positive integer if the first argument is greater than the second. After forcing both Strings to lower case, the String.compareTo( ) method produces the desired results. When you use your own Comparator to perform a sort( ), you must use that same Comparator when using binarySearch( ). The Arrays class has another sort( ) method that takes a single argument: an array of Object, but with no Comparator. This sort( ) method must also have some way to compare two Objects. It uses the natural comparison method that is imparted to a class by implementing the Comparable interface. This interface has a single method, compareTo( ), which compares the object to its argument and returns negative, zero, or positive depending on whether it is less than, equal to, or greater than the argument. Lists A List can be sorted and searched in the same fashion as an array. The TreeMap must also order its objects according to Comparable or Comparator. Utilities There are a number of other useful utilities in the Collections class: enumeration(Collection) Produces an old-style Enumeration for the argument. Note that min( ) and max( ) work with Collection objects, not with Lists, so you don't need to worry about whether the Collection should be sorted or not. The Collections class allows you to do this by passing the original container into a method that hands back a read-only version. There are four variations on this method, one each for Collection (if you don't want to treat a Collection as a more specific type), List, Set, and Map. Once it is loaded, the best approach is to replace the existing handle with the handle that is produced by the unmodifiable call. That way, you don't run the risk of accidentally changing the contents once you've made it unmodifiable. On the other hand, this tool also allows you to keep a modifiable container as private within a class and to return a read-only handle to that container from a method call. So you can change it from within the class but everyone else can only read it. Synchronizing a Collection or Map The synchronized keyword is an important part of the subject of multithreading, a more complicated topic that will not be introduced until Chapter 14. Here, I shall note only that the Collections class contains a way to automatically synchronize an entire container. The new collections also have a mechanism to prevent more than one process from modifying the contents of a container. The problem occurs if you're iterating through a container and some other process steps in and inserts, removes, or changes an object in that container. Maybe you've already passed that object, maybe it's ahead of you, maybe the size of the container shrinks after you call size( ) - there are many scenarios for disaster. The new collections library incorporates a fail fast mechanism that looks for any changes to the container other than the ones your process is personally responsible for. If it detects that someone else is modifying the container, it immediately produces a ConcurrentModificationException. This is the fail-fast aspect - it doesn't try to detect a problem later on using a more complex algorithm. Summary To review the collections provided in the standard Java (1.0 and 1.1) library (BitSet is not included here since it's more of a special-purpose class): 1. An array associates numerical indices to objects. It holds objects of a known type so you don't have to cast the result when you're looking up an object. It can be multidimensional, and it can hold primitives. However, its size cannot be changed once you create it. 2. A Vector also associates numerical indices to objects - you can think of arrays and Vectors as random-access collections. The Vector automatically resizes itself as you add more elements. But a Vector can hold only Object handles, so it won't hold primitives and you must always cast the result when you pull an Object handle out of a collection. 3. A Hashtable is a type of Dictionary, which is a way to associate, not numbers, but objects with other objects. A Hashtable also supports random access to objects, in fact, its whole design is focused around rapid access. 4. A Stack is a last-in, first-out (LIFO) queue. If you're familiar with data structures, you might wonder why there's not a larger set of collections. From a functionality standpoint, do you really need a larger set of collections? With a Hashtable, you can put things in and find them quickly, and with an Enumeration, you can iterate through the sequence and perform an operation on every element in the sequence. That's a powerful tool, and maybe it should be enough. But a Hashtable has no concept of order. Vectors and arrays give you a linear order, but it's expensive to insert an element into the middle of either one. In addition, queues, dequeues, priority queues, and trees are about ordering the elements, not just putting them in and later finding them or moving through them linearly. If you can use Java 1.2 you should use only the new collections, which are likely to satisfy all your needs. Note that the bulk of this book was created using Java 1.1, so you'll see that the collections used through the rest of the book are the ones that are available only in Java 1.1: Vector and Hashtable. This is a somewhat painful restriction at times, but it provides better backward compatibility with older Java code. If you're writing new code in Java 1.2, the new collections will serve you much better. Exercises 1. Create a new class called Gerbil with an int gerbilNumber that's initialized in the constructor (similar to the Mouse example in this chapter). Give it a method called hop( ) that prints out which gerbil number this is and that it's hopping. Create a Vector and add a bunch of Gerbil objects to the Vector. Now use the elementAt( ) method to move through the Vector and call hop( ) for each Gerbil. 2. Modify Exercise 1 so you use an Enumeration to move through the Vector while calling hop( ). 3. In AssocArray.java, change the example so it uses a Hashtable instead of an AssocArray. 4. Take the Gerbil class in Exercise 1 and put it into a Hashtable instead, associating the name of the Gerbil as a String (the key) for each Gerbil (the value) you put in the table. Get an Enumeration for the keys( ) and use it to move through the Hashtable, looking up the Gerbil for each key and printing out the key and telling the gerbil to hop( ). 5. Change Exercise 1 in Chapter 7 to use a Vector to hold the Rodents and an Enumeration to move through the sequence of Rodents. Remember that a Vector holds only Objects so you must use a cast (i.e.: RTTI) when accessing individual Rodents. 6. (Intermediate) In Chapter 7, locate the GreenhouseControls.java example, which consists of three files. In Controller.java, the class EventSet is just a collection. Change the code to use a Stack instead of an EventSet. This will require more than just replacing EventSet with Stack; you'll also need to use an Enumeration to cycle through the set of events. You'll probably find it easier if at times you treat the collection as a Stack and at other times as a Vector. 7. (Challenging). Find the source code for Vector in the Java source code library that comes with all Java distributions. Copy this code and make a special version called intVector that holds only ints. Consider what it would take to make a special version of Vector for all the primitive types. Now consider what happens if you want to make a linked list class that works with all the primitive types. If parameterized types are ever implemented in Java, they will provide a way to do this work for you automatically (as well as many other benefits). 7. ? 9: Error handling with exceptions The basic philosophy of Java is that badly-formed code will not be run. As with C++, the ideal time to catch the error is at compile time, before you even try to run the program. However, not all errors can be detected at compile time. The rest of the problems must be handled at run-time through some formality that allows the originator of the error to pass appropriate information to a recipient who will know how to handle the difficulty properly. In C and other earlier languages, there could be several of these formalities, and they were generally established by convention and not as part of the programming language. Typically, you returned a special value or set a flag, and the recipient was supposed to look at the value or the flag and determine that something was amiss. If you were thorough enough to check for an error every time you called a method, your code could turn into an unreadable nightmare. Because programmers could still coax systems out of these languages they were resistant to admitting the truth: This approach to handling errors was a major limitation to creating large, robust, maintainable programs. The solution is to take the casual nature out of error handling and to enforce formality. This actually has a long history, since implementations of exception handling go back to operating systems in the 1960s and even to BASIC's on error goto. But C++ exception handling was based on Ada, and Java's is based primarily on C++ (although it looks even more like Object Pascal). But you don't have enough information in the current context to fix the problem. So you hand the problem out to a higher context where someone is qualified to make the proper decision (much like a chain of command). The other rather significant benefit of exceptions is that they clean up error handling code. Instead of checking for a particular error and dealing with it at multiple places in your program, you no longer need to check at the point of the method call (since the exception will guarantee that someone catches it). And, you need to handle the problem in only one place, the so-called exception handler. This saves you code and it separates the code that describes what you want to do from the code that is executed when things go awry. In general, reading, writing, and debugging code becomes much clearer with exceptions than when using the old way. Because exception handling is enforced by the Java compiler, there are only so many examples that can be written in this book without learning about exception handling. This chapter introduces you to the code you need to write to properly handle the exceptions, and the way you can generate your own exceptions if one of your methods gets into trouble. Basic exceptions An exceptional condition is a problem that prevents the continuation of the method or scope that you're in. It's important to distinguish an exceptional condition from a normal problem, in which you have enough information in the current context to somehow cope with the difficulty. With an exceptional condition, you cannot continue processing because you don't have the information necessary to deal with the problem in the current context. All you can do is jump out of the current context and relegate that problem to a higher context. This is what happens when you throw an exception. A simple example is a divide. If you're about to divide by zero, it's worth checking to make sure you don't go ahead and perform the divide. But what does it mean that the denominator is zero? Maybe you know, in the context of the problem you're trying to solve in that particular method, how to deal with a zero denominator. But if it's an unexpected value, you can't deal with it and so must throw an exception rather than continuing along that path. When you throw an exception, several things happen. First, the exception object is created in the same way that any Java object is created: on the heap, with new. Then the current path of execution (the one you couldn't continue, remember) is stopped and the handle for the exception object is ejected from the current context. At this point the exception-handling mechanism takes over and begins to look for an appropriate place to continue executing the program. This appropriate place is the exception handler, whose job is to recover from the problem so the program can either try another tack or simply continue. As a simple example of throwing an exception, consider an object handle called t. It's possible that you might be passed a handle that hasn't been initialized, so you might want to check before trying to call a method using that object handle. You can send information about the error into a larger context by creating an object representing your information and throwing it out of your current context. This is called throwing an exception. Here's what it looks like: if(t == null) throw new NullPointerException(); This throws the exception, which allows you - in the current context - to abdicate responsibility for thinking about the issue further. It's just magically handled somewhere else. Precisely where will be shown shortly. Exception arguments Like any object in Java, you always create exceptions on the heap using new and a constructor gets called. The keyword throw causes a number of relatively magical things to happen. First it executes the new-expression to create an object that isn't there under normal program execution, and of course, the constructor is called for that object. Then the object is, in effect, returned from the method, even though that object type isn't normally what the method is designed to return. A simplistic way to think about exception handling is as an alternate return mechanism, although you get into trouble if you take that analogy too far. You can also exit from ordinary scopes by throwing an exception. But a value is returned, and the method or scope exits. Any similarity to an ordinary return from a method ends here, because where you return is someplace completely different from where you return for a normal method call. Typically, you'll throw a different class of exception for each different type of error. The idea is to store the information in the exception object and in the type of exception object chosen, so someone in the bigger context can figure out what to do with your exception. One of the advantages of Java exception handling is that it allows you to concentrate on the problem you're trying to solve in one place, and then deal with the errors from that code in another place. To see how an exception is caught, you must first understand the concept of a guarded region, which is a section of code that might produce exceptions, and is followed by the code to handle those exceptions. The try block If you're inside a method and you throw an exception (or another method you call within this method throws an exception), that method will exit in the process of throwing. If you don't want a throw to leave a method, you can set up a special block within that method to capture the exception. This is called the try block because you try your various method calls there. With exception handling, you put everything in a try block and capture all the exceptions in one place. This means your code is a lot easier to write and easier to read because the goal of the code is not confused with the error checking. Exception handlers Of course, the thrown exception must end up someplace. This place is the exception handler, and there's one for every exception type you want to catch. The identifier (id1, id2, and so on) can be used inside the handler, just like a method argument. Sometimes you never use the identifier because the type of the exception gives you enough information to deal with the exception, but the identifier must still be there. The handlers must appear directly after the try block. If an exception is thrown, the exception-handling mechanism goes hunting for the first handler with an argument that matches the type of the exception. Then it enters that catch clause, and the exception is considered handled. Note that, within the try block, a number of different method calls might generate the same exception, but you need only one handler. Termination vs. In termination (which is what Java and C++ support), you assume the error is so critical there's no way to get back to where the exception occurred. Whoever threw the exception decided that there was no way to salvage the situation, and they don't want to come back. The alternative is called resumption. It means that the exception handler is expected to do something to rectify the situation, and then the faulting method is retried, presuming success the second time. If you want resumption, it means you still hope to continue execution after the exception is handled. In this case, your exception is more like a method call - which is how you should set up situations in Java in which you want resumption-like behavior. Historically, programmers using operating systems that supported resumptive exception handling eventually ended up using termination-like code and skipping resumption. So although resumption sounds attractive at first, it seems it isn't quite so useful in practice. The dominant reason is probably the coupling that results: your handler must often be aware of where the exception is thrown from and contain non-generic code specific to the throwing location. This makes the code difficult to write and maintain, especially for large systems where the exception can be generated from many points. The exception specification In Java, you're required to inform the client programmer, who calls your method, of the exceptions that might be thrown from your method. This is civilized because the caller can know exactly what code to write to catch all potential exceptions. Of course, if source code is available, the client programmer could hunt through and look for throw statements, but often a library doesn't come with sources. To prevent this from being a problem, Java provides syntax (and forces you to use that syntax) to allow you to politely tell the client programmer what exceptions this method throws, so the client programmer can handle them. This is the exception specification and it's part of the method declaration, appearing after the argument list. The exception specification uses an additional keyword, throws, followed by a list of all the potential exception types. By enforcing exception specifications from top to bottom, Java guarantees that exception correctness can be ensured at compile time.2 There is one place you can lie: you can claim to throw an exception that you don't. The compiler takes your word for it and forces the users of your method to treat it as if it really does throw that exception. This has the beneficial effect of being a placeholder for that exception, so you can actually start throwing the exception later without requiring changes to existing code. Catching any exception It is possible to create a handler that catches any type of exception. String toString( ) Returns a short description of the Throwable, including the detail message if there is one. The call stack shows the sequence of method calls that brought you to the point at which the exception was thrown. The first version prints to standard error, the second prints to a stream of your choice. If you're working under Windows, you can't redirect standard error so you might want to use the second version and send the results to System.out; that way the output can be redirected any way you want. In addition, you get some other methods from Throwable's base type Object (everybody's base type). The one that might come in handy for exceptions is getClass( ), which returns an object representing the class of this object. You can in turn query this Class object for its name with getName( ) or toString( ). You can also do more sophisticated things with Class objects that aren't necessary in exception handling. Class objects will be studied later in the book. Rethrowing an exception Sometimes you'll want to rethrow the exception that you just caught, particularly when you use Exception to catch any exception. Any further catch clauses for the same try block are still ignored. In addition, everything about the exception object is preserved, so the handler at the higher context that catches the specific exception type can extract all the information from that object. If you simply re-throw the current exception, the information that you print about that exception in printStackTrace( ) will pertain to the exception's origin, not the place where you re-throw it. If you want to install new stack trace information, you can do so by calling fillInStackTrace( ), which returns an exception object that it creates by stuffing the current stack information into the old exception object. The class Throwable must appear in the exception specification for g( ) and main( ) because fillInStackTrace( ) produces a handle to a Throwable object. Since Throwable is a base class of Exception, it's possible to get an object that's a Throwable but not an Exception, so the handler for Exception in main( ) might miss it. To make sure everything is in order, the compiler forces an exception specification for Throwable. Note that Throwable isn't necessary in any of the exception specifications. You never have to worry about cleaning up the previous exception, or any exceptions for that matter. They're all heap-based objects created with new, so the garbage collector automatically cleans them all up. Standard Java exceptions Java contains a class called Throwable that describes anything that can be thrown as an exception. There are two general types of Throwable objects (types of = inherited from). Error represents compile-time and system errors that you don't worry about catching (except in special cases). Exception is the basic type that can be thrown from any of the standard Java library class methods and from your methods and run-time accidents. Also, the number of exceptions in Java keeps expanding; basically it's pointless to print them in a book. Any new library you get from a third-party vendor will probably have its own exceptions as well. The important thing to understand is the concept and what you should do with the exceptions. Other exceptions are derived from this. The basic idea is that the name of the exception represents the problem that occurred and the exception name is intended to be relatively self-explanatory. The exceptions are not all defined in java.lang; some are created to support other libraries such as util, net, and io, which you can see from their full class names or what they are inherited from. For example, all IO exceptions are inherited from java.io.IOException. Fortunately, you don't - this is part of the standard run-time checking that Java performs for you, and if any call is made to a null handle, Java will automatically throw a NullPointerException. So the above bit of code is always superfluous. There's a whole group of exception types that are in this category. They're always thrown automatically by Java and you don't need to include them in your exception specifications. Also, you never need to write an exception specification saying that a method might throw a RuntimeException, since that's just assumed. Because they indicate bugs, you virtually never catch a RuntimeException - it's dealt with automatically. If you were forced to check for RuntimeExceptions your code could get messy. Even though you don't typically catch RuntimeExceptions, in your own packages you might choose to throw some of the RuntimeExceptions. What happens when you don't catch such exceptions? Since the compiler doesn't enforce exception specifications for these, it's quite plausible that a RuntimeException could percolate all the way out to your main( ) method without being caught. Keep in mind that it's possible to ignore only RuntimeExceptions in your coding, since all other handling is carefully enforced by the compiler. You can see what a tremendous benefit it is to have exceptions in this case, since they help in the debugging process. It's interesting to notice that you cannot classify Java exception handling as a single-purpose tool. Yes, it is designed to handle those pesky run-time errors that will occur because of forces outside your code's control, but it's also essential for certain types of programming bugs that the compiler cannot detect. Creating your own exceptions You're not stuck using the Java exceptions. This is important because you'll often need to create your own exceptions to denote a special error that your library is capable of creating, but which was not foreseen when the Java hierarchy was created. To create your own exception class, you're forced to inherit from an existing type of exception, preferably one that is close in meaning to your new exception. Remember that the compiler automatically calls the base-class default constructor if you don't explicitly call a base-class constructor, as in the MyException( ) default constructor. In the second constructor, the base-class constructor with a String argument is explicitly invoked by using the super keyword. The process of creating your own exceptions can be taken further. Keep in mind, however, that all this dressing up might be lost on the client programmers using your packages, since they might simply look for the exception to be thrown and nothing more. Of course, in this case you don't get a SimpleException(String) constructor, but in practice that isn't used much. Exception restrictions When you override a method, you can throw only the exceptions that have been specified in the base-class version of the method. This is a useful restriction, since it means that code that works with the base class will automatically work with any object derived from the base class (a fundamental OOP concept, of course), including exceptions. This is legal because it allows you to force the user to catch any exceptions that you might add in overridden versions of event( ). The same idea holds for abstract methods, as seen in atBat( ). The interface Storm is interesting because it contains one method (event( ))that is defined in Inning, and one method that isn't. Both methods throw a new type of exception, RainedOut. When StormyInning extends Inning and implements Storm, you'll see that the event( ) method in Storm cannot change the exception interface of event( ) in Inning. Again, this makes sense because otherwise you'd never know if you were catching the correct thing when working with the base class. Of course, if a method described in an interface is not in the base class, such as rainHard( ), then there's no problem if it throws exceptions. The restriction on exceptions does not apply to constructors. You can see in StormyInning that a constructor can throw anything it wants, regardless of what the base-class constructor throws. The reason StormyInning.walk( ) will not compile is that it throws an exception, while Inning.walk( ) does not. By forcing the derived-class methods to conform to the exception specifications of the base-class methods, substitutability of objects is maintained. The overridden event( ) method shows that a derived-class version of a method may choose to not throw any exceptions, even if the base-class version does. Again, this is fine since it doesn't break any code that is written assuming the base-class version throws exceptions. Similar logic applies to atBat( ), which throws PopFoul, an exception that is derived from Foul thrown by the base-class version of atBat( ). This way, if someone writes code that works with Inning and calls atBat( ), they must catch the Foul exception. Since PopFoul is derived from Foul, the exception handler will also catch PopFoul. The last point of interest is in main( ). Therefore, you cannot overload methods based on exception specifications. Put another way, the exception specification interface for a particular method may narrow during inheritance and overriding, but it may not widen - this is precisely the opposite of the rule for the class interface during inheritance. Performing cleanup with finally There's often some piece of code that you want to execute whether or not an exception occurs in a try block. This usually pertains to some operation other than memory recovery (since that's taken care of by the garbage collector). To achieve this effect, you use a finally clause4 at the end of all the exception handlers. If you place your try block in a loop, you can establish a condition that must be met before you continue the program. You can also add a static counter or some other device to allow the loop to try several different approaches before giving up. This way you can build a greater level of robustness into your programs. The output is: Exception thrown in finally clause No exception in finally clause Whether an exception is thrown or not, the finally clause is always executed. What's finally for? In a language without garbage collection and without automatic destructor calls,5 finally is important because it allows the programmer to guarantee the release of memory regardless of what happens in the try block. But Java has garbage collection, so releasing memory is virtually never a problem. Also, it has no destructors to call. So when do you need to use finally in Java? But it's possible that an exception could be thrown that isn't caught here, so sw.off( ) would be missed. Note that, along with the labeled break and labeled continue, finally eliminates the need for a goto statement in Java. Pitfall: the lost exception In general, Java's exception implementation is quite outstanding, but unfortunately there's a flaw. Although exceptions are an indication of a crisis in your program and should never be ignored, it's possible for an exception to simply be lost. This is a rather serious pitfall, since it means that an exception can be completely lost, and in a far more subtle and difficult-to-detect fashion than the example above. In contrast, C++ treats the situation in which a second exception is thrown before the first one is handled as a dire programming error. Perhaps a future version of Java will repair the problem. The constructor puts the object into a safe starting state, but it might perform some operation - such as opening a file - that doesn't get cleaned up until the user is finished with the object and calls a special cleanup method. If you throw an exception from inside a constructor, these cleanup behaviors might not occur properly. This means that you must be especially diligent while you write your constructor. Since you've just learned about finally, you might think that it is the correct solution. But it's not quite that simple, because finally performs the cleanup code every time, even in the situations in which you don't want the cleanup code executed until the cleanup method runs. Thus, if you do perform cleanup in finally, you must set some kind of flag when the constructor finishes normally and don't do anything in the finally block if the flag is set. Because this isn't particularly elegant (you are coupling your code from one place to another), it's best if you try to avoid performing this kind of cleanup in finally unless you are forced to. In the following example, a class called InputFile is created that opens a file and allows you to read it one line (converted into a String) at a time. The constructor for InputFile takes a String argument, which is the name of the file you want to open. Inside a try block, it creates a FileReader using the file name. A FileReader isn't particularly useful until you turn around and use it to create a BufferedReader that you can actually talk to - notice that one of the benefits of InputFile is that it combines these two actions. If the FileReader constructor is unsuccessful, it throws a FileNotFoundException, which must be caught separately because that's the one case in which you don't want to close the file since it wasn't successfully opened. Any other catch clauses must close the file because it was opened by the time those catch clauses are entered. After performing local operations, the exception is re-thrown, which is appropriate because this constructor failed, and you wouldn't want the calling method to assume that the object had been properly created and was valid. In this example, which doesn't use the aforementioned flagging technique, the finally clause is definitely not the place to close( ) the file, since that would close it every time the constructor completed. Since we want the file to be open for the useful lifetime of the InputFile object this would not be appropriate. The getLine( ) method returns a String containing the next line in the file. It calls readLine( ), which can throw an exception, but that exception is caught so getLine( ) doesn't throw any exceptions. One of the design issues with exceptions is whether to handle an exception completely at this level, to handle it partially and pass the same exception (or a different one) on, or whether to simply pass it on. Passing it on, when appropriate, can certainly simplify coding. You might think of putting such functionality into a finalize( ) method, but as mentioned in Chapter 4 you can't always be sure that finalize( ) will be called (even if you can be sure that it will be called, you don't know when). This is one of the downsides to Java - all cleanup other than memory cleanup doesn't happen automatically, so you must inform the client programmer that they are responsible, and possibly guarantee that cleanup occurs using finalize( ). In Cleanup.java an InputFile is created to open the same source file that creates the program, and this file is read in a line at a time, and line numbers are added. All exceptions are caught generically in main( ), although you could choose greater granularity. One of the benefits of this example is to show you why exceptions are introduced at this point in the book. Exceptions are so integral to programming in Java, especially because the compiler enforces them, that you can accomplish only so much without knowing how to work with them. Exception matching When an exception is thrown, the exception-handling system looks through the nearest handlers in the order they are written. When it finds a match, the exception is considered handled, and no further searching occurs. Matching an exception doesn't require a perfect match between the exception and its handler. Put another way, catch(Annoyance e) will catch a Annoyance or any class derived from it. This is useful because if you decide to add more exceptions to a method, if they're all inherited from the same base class then the client programmer's code will not need changing, assuming they catch the base class, at the very least. Exception guidelines Use exceptions to: 1. Fix the problem and call the method (which caused the exception) again. 2. Patch things up and continue without retrying the method. 3. Calculate some alternative result instead of what the method was supposed to produce. 4. Do whatever you can in the current context and rethrow the same exception to a higher context. 5. Do whatever you can in the current context and throw a different exception to a higher context. 6. Terminate the program. 7. Simplify. If your exception scheme makes things more complicated, then it is painful and annoying to use. 8. Make your library and program safer. This is a short-term investment (for debugging) and a long-term investment (for application robustness). Summary Improved error recovery is one of the most powerful ways that you can increase the robustness of your code. Error recovery is a fundamental concern for every program you write, and it's especially important in Java, in which one of the primary goals is to create program components for others to use. To create a robust system, each component must be robust. The goals for exception handling in Java are to simplify the creation of large, reliable programs using less code than currently possible, with more confidence that your application doesn't have an unhandled error. Exceptions are not terribly difficult to learn, and are one of those features that provide immediate and significant benefits to your project. Fortunately, Java enforces all aspects of exceptions so it's guaranteed that they will be used consistently by both library designer and client programmer. Exercises 1. Create a class with a main( ) that throws an object of class Exception inside a try block. Give the constructor for Exception a string argument. Catch the exception inside a catch clause and print out the string argument. Add a finally clause and print a message to prove you were there. 2. Create your own exception class using the extends keyword. Write a constructor for this class that takes a String argument and stores it inside the object with a String handle. Write a method that prints out the stored String. Create a try-catch clause to exercise your new exception. 3. Write a class with a method that throws an exception of the type created in Exercise 2. Try compiling it without an exception specification to see what the compiler says. Add the appropriate exception specification. Try out your class and its exception inside a try-catch clause. 4. In chapter 5, find the two programs called Assert.java and modify these to throw their own type of exception instead of printing to System.err. This exception should be an inner class that extends RuntimeException. This is evidenced by the number of different approaches. The challenge seems to be in covering all eventualities. In fact, there are so many classes for Java's IO system that it can be intimidating at first (ironically, the Java IO design actually prevents an explosion of classes). There has also been a significant change in the IO library between Java 1.0 and Java 1.1. Instead of simply replacing the old library with a new one, the designers at Sun extended the old library and added the new one alongside it. As a result you can sometimes end up mixing the old and new libraries and creating even more intimidating code. This chapter will help you understand the variety of IO classes in the standard Java library and how to use them. The first portion of the chapter will introduce the old Java 1.0 IO stream library, since there is a significant amount of existing code that uses that library. The remainder of the chapter will introduce the new features in the Java 1.1 IO library. Note that when you compile some of the code in the first part of the chapter with a Java 1.1 compiler you can get a deprecated feature warning message at compile time. The code still works; the compiler is just suggesting that you use certain new features that are described in the latter part of this chapter. It is valuable, however, to see the difference between the old and new way of doing things and that's why it was left in - to increase your understanding (and to allow you to read code written for Java 1.0). Input and output The Java library classes for IO are divided by input and output, as you can see by looking at the online Java class hierarchy with your Web browser. By inheritance, all classes derived from InputStream have basic methods called read( ) for reading a single byte or array of bytes. Likewise, all classes derived from OutputStream have basic methods called write( ) for writing a single byte or array of bytes. However, you won't generally use these methods; they exist so more sophisticated classes can use them as they provide a more useful interface. Thus, you'll rarely create your stream object by using a single class, but instead will layer multiple objects together to provide your desired functionality. The fact that you create more than one object to create a single resulting stream is the primary reason that Java's stream library is confusing. It's helpful to categorize the classes by their functionality. The library designers started by deciding that all classes that had anything to do with input would be inherited from InputStream and all classes that were associated with output would be inherited from OutputStream. Types of InputStream InputStream's job is to represent classes that produce input from different sources. This is discussed later. Table 10-1. Types of InputStream Class Function Constructor Arguments How to use it ByteArray-InputStream Allows a buffer in memory to be used as an InputStream. The buffer from which to extract the bytes. As a source of data. Connect it to a FilterInputStream object to provide a useful interface. StringBuffer-InputStream Converts a String into an InputStream. A String. The underlying implementation actually uses a StringBuffer. As a source of data. Connect it to a FilterInputStream object to provide a useful interface. File-InputStream For reading information from a file. A String representing the file name, or a File or FileDescriptor object. As a source of data. Connect it to a FilterInputStream object to provide a useful interface. Piped-InputStream Produces the data that's being written to the associated PipedOutput-Stream. Implements the piping concept. PipedOutputStream As a source of data in multithreading. Connect it to a FilterInputStream object to provide a useful interface. Sequence-InputStream Coverts two or more InputStream objects into a single InputStream. Two InputStream objects or an Enumeration for a container of InputStream objects. As a source of data. Connect it to a FilterInputStream object to provide a useful interface. Filter-InputStream Abstract class which is an interface for decorators that provide useful functionality to the other InputStream classes. See Table 10-3. See Table 10-3. See Table 10-3. This is discussed later. Table 10-2. Types of OutputStream Class Function Constructor Arguments How to use it ByteArray-OutputStream Creates a buffer in memory. All the data that you send to the stream is placed in this buffer. Optional initial size of the buffer. To designate the destination of your data. Connect it to a FilterOutputStream object to provide a useful interface. File-OutputStream For sending information to a file. A String representing the file name, or a File or FileDescriptor object. To designate the destination of your data. Connect it to a FilterOutputStream object to provide a useful interface. Piped-OutputStream Any information you write to this automatically ends up as input for the associated PipedInput-Stream. Implements the piping concept. PipedInputStream To designate the destination of your data for multithreading. Connect it to a FilterOutputStream object to provide a useful interface. Filter-OutputStream Abstract class which is an interface for decorators that provide useful functionality to the other OutputStream classes. See Table 10-4. See Table 10-4. See Table 10-4. Adding attributes and useful interfaces The use of layered objects to dynamically and transparently add responsibilities to individual objects is referred to as the decorator pattern. This is the reason for the existence of the filter classes in the Java IO library: the abstract filter class is the base class for all the decorators. Decorators are often used when subclassing requires a large number of subclasses to support every possible combination needed - so many that subclassing becomes impractical. The Java IO library requires many different combinations of features which is why the decorator pattern is a good approach. There is a drawback to the decorator pattern, however. Decorators give you much more flexibility while you're writing a program (since you can easily mix and match attributes), but they add complexity to your code. The reason that the Java IO library is awkward to use is that you must create many classes - the core IO type plus all the decorators - in order to get the single IO object that you want. The classes that provide the decorator interface to control a particular InputStream or OutputStream are the FilterInputStream and FilterOutputStream - which don't have very intuitive names. They are derived, respectively, from InputStream and OutputStream, and they are abstract classes, in theory to provide a common interface for all the different ways you want to talk to a stream. In fact, FilterInputStream and FilterOutputStream simply mimic their base classes, which is the key requirement of the decorator. Reading from an InputStream with FilterInputStream The FilterInputStream classes accomplish two significantly different things. DataInputStream allows you to read different types of primitive data as well as String objects. These places are determined by the classes in Table 10-1. If you're reading data in blocks and parsing it yourself, you won't need DataInputStream, but in most other cases you will want to use it to automatically format the data you read. The last two classes look a lot like support for building a compiler (that is, they were added to support the construction of the Java compiler), so you probably won't use them in general programming. You'll probably need to buffer your input almost every time, regardless of the IO device you're connecting to, so it would have made more sense for the IO library to make a special case for unbuffered input rather than buffered input. Table 10-3. Types of FilterInputStream Class Function Constructor Arguments How to use it Data-InputStream Used in concert with DataOutputStream, so you can read primitives (int, char, long, etc.) from a stream in a portable fashion. InputStream Contains a full interface to allow you to read primitive types. Buffered-InputStream Use this to prevent a physical read every time you want more data. You're saying Use a buffer. InputStream, with optional buffer size. This doesn't provide an interface per se, just a requirement that a buffer be used. Attach an interface object. LineNumber-InputStream Keeps track of line numbers in the input stream; you can call getLineNumber( ) and setLineNumber(int). InputStream This just adds line numbering, so you'll probably attach an interface object. Pushback-InputStream Has a one byte push-back buffer so that you can push back the last character read. InputStream Generally used in the scanner for a compiler and probably included because the Java compiler needed it. You probably won't use this. All the methods start with write, such as writeByte( ), writeFloat( ), etc. If you want to do true formatted output, for example, to the console, use a PrintStream. The System.out static object is a PrintStream. The two important methods in PrintStream are print( ) and println( ), which are overloaded to print out all the various types. The difference between print( ) and println( ) is that the latter adds a newline when it's done. BufferedOutputStream is a modifier and tells the stream to use buffering so you don't get a physical write every time you write to the stream. You'll probably always want to use this with files, and possibly console IO. Table 10-4. Types of FilterOutputStream Class Function Constructor Arguments How to use it Data-OutputStream Used in concert with DataInputStream so you can write primitives (int, char, long, etc.) to a stream in a portable fashion. OutputStream Contains full interface to allow you to write primitive types. PrintStream For producing formatted output. While DataOutputStream handles the storage of data, PrintStream handles display. OutputStream, with optional boolean indicating that the buffer is flushed with every newline. Should be the final wrapping for your OutputStream object. You'll probably use this a lot. Buffered-OutputStream Use this to prevent a physical write every time you send a piece of data. You're saying Use a buffer. You can call flush( ) to flush the buffer. OutputStream, with optional buffer size. This doesn't provide an interface per se, just a requirement that a buffer is used. Attach an interface object. Off by itself: RandomAccessFile RandomAccessFile is used for files containing records of known size so that you can move from one record to another using seek( ), then read or change the records. The records don't have to be the same size; you just have to be able to determine how big they are and where they are placed in the file. At first it's a little bit hard to believe that RandomAccessFile is not part of the InputStream or OutputStream hierarchy. It has no association with those hierarchies other than that it happens to implement the DataInput and DataOutput interfaces (which are also implemented by DataInputStream and DataOutputStream). It doesn't even use any of the functionality of the existing InputStream or OutputStream classes - it's a completely separate class, written from scratch, with all of its own (mostly native) methods. The reason for this may be that RandomAccessFile has essentially different behavior than the other IO types, since you can move forward and backward within a file. In any event, it stands alone, as a direct descendant of Object. In addition, the constructors require a second argument (identical to fopen( ) in C) indicating whether you are just randomly reading (r) or reading and writing (rw). There's no support for write-only files, which could suggest that RandomAccessFile might have worked well if it were inherited from DataInputStream. What's even more frustrating is that you could easily imagine wanting to seek within other types of streams, such as a ByteArrayInputStream, but the seeking methods are available only in RandomAccessFile, which works for files only. BufferedInputStream does allow you to mark( ) a position (whose value is held in a single internal variable) and reset( ) to that position, but this is limited and not too useful. The File class The File class has a deceiving name - you might think it refers to a file, but it doesn't. It can represent either the name of a particular file or the names of a set of files in a directory. If it's a set of files, you can ask for the set with the list( ) method, and this returns an array of String. It makes sense to return an array rather than one of the flexible collection classes because the number of elements is fixed, and if you want a different directory listing you just create a different File object. In fact, FilePath would have been a better name. This section shows a complete example of the use of this class, including the associated FilenameFilter interface. A directory lister Suppose you'd like to see a directory listing. The File object can be listed in two ways. If you call list( ) with no arguments, you'll get the full list that the File object contains. However, if you want a restricted list, for example, all of the files with an extension of.java, then you use a directory filter, which is a class that tells how to select the File objects for display. The whole reason behind the creation of this class is to provide the accept( ) method to the list( ) method so that list( ) can call back accept( ) to determine which file names should be included in the list. Thus, this technique is often referred to as a callback or sometimes a functor (that is, DirFilter is a functor because its only job is to hold a method). Because list( ) takes a FilenameFilter object as its argument, it means that you can pass an object of any class that implements FilenameFilter to choose (even at run-time) how the list( ) method will behave. The purpose of a callback is to provide flexibility in the behavior of code. DirFilter shows that just because an interface contains only a set of methods, you're not restricted to writing only those methods. The accept( ) method must accept a File object representing the directory that a particular file is found in, and a String containing the name of that file. You might choose to use or ignore either of these arguments, but you will probably at least use the file name. Remember that the list( ) method is calling accept( ) for each of the file names in the directory object to see which one should be included - this is indicated by the boolean result returned by accept( ). Then accept( ) uses the String class indexOf( ) method to see if the search string afn appears anywhere in the name of the file. If afn is found within the string, the return value is the starting index of afn, but if it's not found the return value is -1. Keep in mind that this is a simple string search and does not have regular expression wildcard matching such as fo?.b?r* which is much more difficult to implement. The list( ) method returns an array. You can query this array for its length and then move through it selecting the array elements. This ability to easily pass an array in and out of a method is a tremendous improvement over the behavior of C and C++. Anonymous inner classes This example is ideal for rewriting using an anonymous inner class (described in Chapter 7). This is required by the anonymous inner class so that it can use an object from outside its scope. This design is an improvement because the FilenameFilter class is now tightly bound to DirList2. This shows you how anonymous inner classes allow the creation of quick-and-dirty classes to solve problems. Since everything in Java revolves around classes, this can be a useful coding technique. One benefit is that it keeps the code that solves a particular problem isolated together in one spot. On the other hand, it is not always as easy to read, so you must use it judiciously. A sorted directory listing Ah, you say that you want the file names sorted? Instead of creating path and list as local variables to main( ), they are members of the class so their values can be accessible for the lifetime of the object. In fact, main( ) is now just a way to test the class. You can see that the constructor of the class automatically sorts the list once that list has been created. The sort is case-insensitive so you don't end up with a list of all the words starting with capital letters, followed by the rest of the words starting with all the lowercase letters. However, you'll notice that within a group of file names that begin with the same letter the capitalized words are listed first, which is still not quite the desired behavior for the sort. This problem will be fixed in Java 1.2. Checking for and creating directories The File class is more than just a representation for an existing directory path, file, or group of files. You can also use a File object to create a new directory or an entire directory path if it doesn't exist. The first method that's exercised by main( ) is renameTo( ), which allows you to rename (or move) a file to an entirely new path represented by the argument, which is another File object. This also works with directories of any length. If you experiment with the above program, you'll find that you can make a directory path of any complexity because mkdirs( ) will do all the work for you. In Java 1.0, the -d flag reports that the directory is deleted but it's still there; in Java 1.1 the directory is actually deleted. Typical uses of IO streams Although there are a lot of IO stream classes in the library that can be combined in many different ways, there are just a few ways that you'll probably end up using them. However, they require attention to get the correct combinations. The following rather long example shows the creation and use of typical IO configurations so you can use it as a reference when writing your own code. Note that each configuration begins with a commented number and title that corresponds to the heading for the appropriate explanation that follows in the text. Parts 1 through 4 demonstrate the creation and use of input streams (although part 4 also shows the simple use of an output stream as a testing tool). 1. Buffered input file To open a file for input, you use a FileInputStream with a String or a File object as the file name. For speed, you'll want that file to be buffered so you give the resulting handle to the constructor for a BufferedInputStream. To read input in a formatted fashion, you give that resulting handle to the constructor for a DataInputStream, which is your final object and the interface you read from. In this example, only the readLine( ) method is used, but of course any of the DataInputStream methods are available. When you reach the end of the file, readLine( ) returns null so that is used to break out of the while loop. The String s2 is used to accumulate the entire contents of the file (including newlines that must be added since readLine( ) strips them off). Finally, close( ) is called to close the file. Technically, close( ) will be called when finalize( ) is run, and this is supposed to happen (whether or not garbage collection occurs) as the program exits. However, Java 1.0 has a rather important bug, so this doesn't happen. In Java 1.1 you must explicitly call System.runFinalizersOnExit(true) to guarantee that finalize( ) will be called for every object in the system. The safest approach is to explicitly call close( ) for files. 2. Input from memory This piece takes the String s2 that now contains the entire contents of the file and uses it to create a StringBufferInputStream. Note that read( ) returns the next byte as an int and thus it must be cast to a char to print properly. 3. Formatted memory input The interface for StringBufferInputStream is limited, so you usually enhance it by wrapping it inside a DataInputStream. However, if you choose to read the characters out a byte at a time using readByte( ), any value is valid so the return value cannot be used to detect the end of input. Instead, you can use the available( ) method to find out how many more characters are available. You could also detect the end of input in cases like these by catching an exception. However, the use of exceptions for control flow is considered a misuse of that feature. 4. Line numbering and file output This example shows the use of the LineNumberInputStream to keep track of the input line numbers. Here, you cannot simply gang all the constructors together, since you have to keep a handle to the LineNumberInputStream. This example also shows how to write formatted data to a file. First, a FileOutputStream is created to connect to the file. For efficiency, this is made a BufferedOutputStream, which is what you'll virtually always want to do, but you're forced to do it explicitly. Then for the formatting it's turned into a PrintStream. The data file created this way is readable as an ordinary text file. One of the methods that indicates when a DataInputStream is exhausted is readLine( ), which returns null when there are no more strings to read. Each line is printed to the file along with its line number, which is acquired through li. You'll see an explicit close( ) for out1, which would make sense if the program were to turn around and read the same file again. However, this program ends without ever looking at the file IODemo.out. As mentioned before, if you don't call close( ) for all your output files, you might discover that the buffers don't get flushed so they're incomplete. Output streams The two primary kinds of output streams are separated by the way they write data: one writes it for human consumption, and the other writes it to be re-acquired by a DataInputStream. The RandomAccessFile stands alone, although its data format is compatible with the DataInputStream and DataOutputStream. 5. Storing and recovering data A PrintStream formats data so it's readable by a human. To output data so that it can be recovered by another stream, you use a DataOutputStream to write the data and a DataInputStream to recover the data. Of course, these streams could be anything, but here a file is used, buffered for both reading and writing. Note that the character string is written using writeBytes( ) and not writeChars( ). If you use the latter, you'll be writing the 16-bit Unicode characters. Since there is no complementary readChars method in DataInputStream, you're stuck pulling these characters off one at a time with readChar( ). So for ASCII, it's easier to write the characters as bytes followed by a newline; then use readLine( ) to read back the bytes as a regular ASCII line. The writeDouble( ) stores the double number to the stream and the complementary readDouble( ) recovers it. But for any of the reading methods to work correctly, you must know the exact placement of the data item in the stream, since it would be equally possible to read the stored double as a simple sequence of bytes, or as a char, etc. So you must either have a fixed format for the data in the file or extra information must be stored in the file that you parse to determine where the data is located. 6. Reading and writing random access files As previously noted, the RandomAccessFile is almost totally isolated from the rest of the IO hierarchy, save for the fact that it implements the DataInput and DataOutput interfaces. So you cannot combine it with any of the aspects of the InputStream and OutputStream subclasses. Even though it might make sense to treat a ByteArrayInputStream as a random access element, you can use RandomAccessFile to only open a file. You must assume a RandomAccessFile is properly buffered since you cannot add that. The one option you have is in the second constructor argument: you can open a RandomAccessFile to read (r) or read and write (rw). Using a RandomAccessFile is like using a combined DataInputStream and DataOutputStream (because it implements the equivalent interfaces). In addition, you can see that seek( ) is used to move about in the file and change one of the values. Shorthand for file manipulation Since there are certain canonical forms that you'll be using regularly with files, you may wonder why you have to do all of that typing - this is one of the drawbacks of the decorator pattern. This portion shows the creation and use of shorthand versions of typical file reading and writing configurations. These shorthands are placed in the package com.bruceeckel.tools that was begun in Chapter 5 (See page 196). To add each class to the library, simply place it in the appropriate directory and add the package statement. Now you can reduce your chances of repetitive stress syndrome while creating files, as seen in the example. 8. Formatted file output shorthand The same kind of approach can be taken to create a PrintStream that writes to a buffered file. Reading from standard input Following the approach pioneered in Unix of standard input, standard output, and standard error output, Java has System.in, System.out, and System.err. Throughout the book you've seen how to write to standard output using System.out, which is already pre-wrapped as a PrintStream object. System.err is likewise a PrintStream, but System.in is a raw InputStream, with no wrapping. This means that while you can use System.out and System.err right away, System.in must be wrapped before you can read from it. Typically, you'll want to read input a line at a time using readLine( ), so you'll want to wrap System.in in a DataInputStream. This is the old Java 1.0 way to do line input. A bit later in the chapter you'll see the Java 1.1 solution. Note that System.in should also be buffered, as with most streams It's a bit inconvenient that you're forced to wrap System.in in a DataInputStream in each program, but perhaps it was designed this way to allow maximum flexibility. Piped streams The PipedInputStream and PipedOutputStream have been mentioned only briefly in this chapter. This is not to suggest that they aren't useful, but their value is not apparent until you begin to understand multithreading, since the piped streams are used to communicate between threads. This is covered along with an example in Chapter 14. StreamTokenizer Although StreamTokenizer is not derived from InputStream or OutputStream, it works only with InputStream objects, so it rightfully belongs in the IO portion of the library. The StreamTokenizer class is used to break any InputStream into a sequence of tokens, which are bits of text delimited by whatever you choose. For example, your tokens could be words, and then they would be delimited by white space and punctuation. This is easy enough to do with a StrSortVector. In StreamTokenizer, there is a default list of separators, and you can add more with a set of methods. Here, ordinaryChar( ) is used to say This character has no significance that I'm interested in, so the parser doesn't include it as part of any of the words that it creates. For example, saying st.ordinaryChar('.') means that periods will not be included as parts of the words that are parsed. You can find more information in the online documentation that comes with Java. In countWords( ), the tokens are pulled one at a time from the stream, and the ttype information is used to determine what to do with each token, since a token can be an end-of-line, a number, a string, or a single character. Once a token is found, the Hashtable counts is queried to see if it already contains the token as a key. If it does, the corresponding Counter object is incremented to indicate that another instance of this word has been found. If not, a new Counter is created - since the Counter constructor initializes its value to one, this also acts to count the word. SortedWordCount is not a type of Hashtable, so it wasn't inherited. It performs a specific type of functionality, so even though the keys( ) and values( ) methods must be re-exposed, that still doesn't mean that inheritance should be used since a number of Hashtable methods are inappropriate here. In addition, other methods like getCounter( ), which get the Counter for a particular String, and sortedKeys( ), which produces an Enumeration, finish the change in the shape of SortedWordCount's interface. In main( ) you can see the use of a SortedWordCount to open and count the words in a file - it just takes two lines of code. Then an enumeration to a sorted list of keys (words) is extracted, and this is used to pull out each key and associated Count. Note that the call to cleanup( ) is necessary to ensure that the file is closed. A second example using StreamTokenizer can be found in Chapter 17. StringTokenizer Although it isn't part of the IO library, the StringTokenizer has sufficiently similar functionality to StreamTokenizer that it will be described here. The StringTokenizer returns the tokens within a string one at a time. These tokens are consecutive characters delimited by tabs, spaces, and newlines. In general, if you need more sophistication, use a StreamTokenizer. You ask a StringTokenizer object for the next token in the string using the nextToken( ) method, which either returns the token or an empty string to indicate that no tokens remain. As an example, the following program performs a limited analysis of a sentence, looking for key phrase sequences to indicate whether happiness or sadness is implied. I am happy); analyze(I am sad about this); analyze(I am not sad about this); analyze(I am not! I am sad); analyze(Are you happy about this?); analyze(Are you sad about this?); analyze(It's you! I am happy); analyze(It's you! Notice the first if statement, which says to continue (go back to the beginning of the loop and start again) if the token is neither an I nor an Are. This means that it will get tokens until an I or an Are is found. You might think to use the == instead of the equals( ) method, but that won't work correctly, since == compares handle values while equals( ) compares contents. The logic of the rest of the analyze( ) method is that the pattern that's being searched for is I am sad, I am not happy, or Are you sad? Without the break statement, the code for this would be even messier than it is. You should be aware that a typical parser (this is a primitive example of one) normally has a table of these tokens and a piece of code that moves through the states in the table as new tokens are read. You should think of the StringTokenizer only as shorthand for a simple and specific kind of StreamTokenizer. However, if you have a String that you want to tokenize and StringTokenizer is too limited, all you have to do is turn it into a stream with StringBufferInputStream and then use that to create a much more powerful StreamTokenizer. Java 1.1 IO streams At this point you might be scratching your head, wondering if there is another design for IO streams that could require more typing. Could someone have come up with an odder design? Prepare yourself: Java 1.1 makes some significant modifications to the IO stream library. When you see the Reader and Writer classes your first thought (like mine) might be that these were meant to replace the InputStream and OutputStream classes. But that's not the case. As a result there are situations in which you have more layers of wrapping with the new IO stream library than with the old. Again, this is a drawback of the decorator pattern - the price you pay for added flexibility. The most important reason for adding the Reader and Writer hierarchies in Java 1.1 is for internationalization. The old IO stream hierarchy supports only 8-bit byte streams and doesn't handle the 16-bit Unicode characters well. Since Unicode is used for internationalization (and Java's native char is 16-bit Unicode), the Reader and Writer hierarchies were added to support Unicode in all IO operations. In addition, the new libraries are designed for faster operations than the old. As is the practice in this book, I will attempt to provide an overview of the classes but assume that you will use online documentation to determine all the details, such as the exhaustive list of methods. Sources and sinks of data Almost all of the Java 1.0 IO stream classes have corresponding Java 1.1 classes to provide native Unicode manipulation. It would be easiest to say Always use the new classes, never use the old ones, but things are not that simple. Sometimes you are forced into using the Java 1.0 IO stream classes because of the library design; in particular, the java.util.zip libraries are new additions to the old stream library and they rely on old stream components. So the most sensible approach to take is to try to use the Reader and Writer classes whenever you can, and you'll discover the situations when you have to drop back into the old libraries because your code won't compile. Here is a table that shows the correspondence between the sources and sinks of information (that is, where the data physically comes from or goes to) in the old and new libraries. Modifying stream behavior In Java 1.0, streams were adapted for particular needs using decorator subclasses of FilterInputStream and FilterOutputStream. Java 1.1 IO streams continues the use of this idea, but the model of deriving all of the decorators from the same filter base class is not followed. This can make it a bit confusing if you're trying to understand it by looking at the class hierarchy. In the following table, the correspondence is a rougher approximation than in the previous table. Other than this, DataInputStream is still a preferred member of the Java 1.1 IO library. To make the transition to using a PrintWriter easier, it has constructors that take any OutputStream object. However, PrintWriter has no more support for formatting than PrintStream does; the interfaces are virtually the same. There are some important differences, though. First of all, since random access files have not changed, section 6 is not repeated. Section 1 shrinks a bit because if all you're doing is reading line input you need only to wrap a BufferedReader around a FileReader. Section 1b shows the new way to wrap System.in for reading console input, and this expands because System.in is a DataInputStream and BufferedReader needs a Reader argument, so InputStreamReader is brought in to perform the translation. In section 2 you can see that if you have a String and want to read from it you just use a StringReader instead of a StringBufferInputStream and the rest of the code is identical. Section 3 shows a bug in the design of the new IO stream library. If you have a String and you want to read from it, you're not supposed to use a StringBufferInputStream any more. When you compile code involving a StringBufferInputStream constructor, you get a deprecation message telling you to not use it. Instead, you're supposed to use a StringReader. However, if you want to do formatted memory input as in section 3, you're forced to use a DataInputStream - there is no DataReader to replace it - and a DataInputStream constructor requires an InputStream argument. So you have no choice but to use the deprecated StringBufferInputStream class. The compiler will give you a deprecation message but there's nothing you can do about it.2 Section 4 is a reasonably straightforward translation from the old streams to the new, with no surprises. In section 5, you're forced to use all the old streams classes because DataOutputStream and DataInputStream require them and there are no alternatives. However, you don't get any deprecation messages at compile time. If you compare section 5 with that section in IOStreamDemo.java, you'll notice that in this version, the data is written before the text. This is a rather limiting bug, and we can hope that it will be fixed by the time you read this. You should run the above program to test it; if you don't get an exception and the values print correctly then you're out of the woods. Redirecting input is valuable for a command-line program in which you want to test a particular user-input sequence repeatedly. This is another example in which a deprecation message is inevitable. The message you can get when compiling with the -deprecation flag is: Note: The constructor java.io.PrintStream(java.io.OutputStream) has been deprecated. However, both System.setOut( ) and System.setErr( ) require a PrintStream object as an argument, so you are forced to call the PrintStream constructor. It's a mystery. Compression Java 1.1 has also added some classes to support reading and writing streams in a compressed format. These are wrapped around existing IO classes to provide compression functionality. One aspect of these Java 1.1 classes stands out: They are not derived from the new Reader and Writer classes, but instead are part of the InputStream and OutputStream hierarchies. So you might be forced to mix the two types of streams. Thus you can easily manipulate your compressed data with the many tools available for reading and writing these formats. Simple compression with GZIP The GZIP interface is simple and thus is probably more appropriate when you have a single stream of data that you want to compress (rather than a collection of dissimilar pieces of data). All else is ordinary IO reading and writing. This is, however, a good example of when you're forced to mix the old IO streams with the new: in uses the Reader classes, whereas GZIPOutputStream's constructor can accept only an OutputStream object, not a Writer object. Multi-file storage with Zip The Java 1.1 library that supports the Zip format is much more extensive. With it you can easily store multiple files, and there's even a separate class to make the process of reading a Zip file easy. The library uses the standard Zip format so that it works seamlessly with all the tools currently downloadable on the Internet. The following example has the same form as the previous example, but it handles as many command-line arguments as you want. In addition, it shows the use of the Checksum classes to calculate and verify the checksum for the file. There are two Checksum types: Adler32 (which is faster) and CRC32 (which is slower but slightly more accurate). However, even though the Zip format has a way to set a password, this is not supported in Java's Zip library. And although CheckedInputStream and CheckedOutputStream support both Adler32 and CRC32 checksums, the ZipEntry class supports only an interface for CRC. This is a restriction of the underlying Zip format, but it might limit you from using the faster Adler32. To extract files, ZipInputStream has a getNextEntry( ) method that returns the next ZipEntry if there is one. As a more succinct alternative, you can read the file using a ZipFile object, which has a method entries( ) to return an Enumeration to the ZipEntries. In order to read the checksum you must somehow have access to the associated Checksum object. Here, a handle to the CheckedOutputStream and CheckedInputStream objects is retained, but you could also just hold onto a handle to the Checksum object. A baffling method in Zip streams is setComment( ). As shown above, you can set a comment when you're writing a file, but there's no way to recover the comment in the ZipInputStream. Comments appear to be supported fully on an entry-by-entry basis only via ZipEntry. Of course, you are not limited to files when using the GZIP or Zip libraries - you can compress anything, including data to be sent through a network connection. The Java archive (jar) utility The Zip format is also used in the Java 1.1 JAR (Java ARchive) file format, which is a way to collect a group of files into a single compressed file, just like Zip. However, like everything else in Java, JAR files are cross-platform so you don't need to worry about platform issues. You can also include audio and image files as well as class files. JAR files are particularly helpful when you deal with the Internet. Before JAR files, your Web browser would have to make repeated requests of a Web server in order to download all of the files that make up an applet. In addition, each of these files was uncompressed. By combining all of the files for a particular applet into a single JAR file, only one server request is necessary and the transfer is faster because of compression. And each entry in a JAR file can be digitally signed for security (refer to the Java documentation for details). A JAR file consists of a single file containing a collection of zipped files along with a manifest that describes them. The jar utility that comes with Sun's JDK automatically compresses the files of your choice. These are: c Creates a new or empty archive. Path information is also preserved. Here are some typical ways to invoke jar: jar cf myJarFile.jar *.class This creates a JAR file called myJarFile.jar that contains all of the class files in the current directory, along with an automatically-generated manifest file. The verbose flag is also included to give extra feedback while the jar program is working. If you create a JAR file using the O option, that file can be placed in your CLASSPATH: CLASSPATH=lib1.jar;lib2.jar; Then Java can search lib1.jar and lib2.jar for class files. The jar tool isn't as useful as a zip utility. For example, you can't add or update files to an existing JAR file; you can create JAR files only from scratch. Also, you can't move files into a JAR file, erasing them as they are moved. However, a JAR file created on one platform will be transparently readable by the jar tool on any other platform (a problem that sometimes plagues zip utilities). As you will see in Chapter 13, JAR files are also used to package Java Beans. This is even true across a network, which means that the serialization mechanism automatically compensates for differences in operating systems. That is, you can create an object on a Windows machine, serialize it, and send it across the network to a Unix machine where it will be correctly reconstructed. You don't have to worry about the data representations on the different machines, the byte ordering, or any other details. By itself, object serialization is interesting because it allows you to implement lightweight persistence. Remember that persistence means an object's lifetime is not determined by whether a program is executing - the object lives in between invocations of the program. By taking a serializable object and writing it to disk, then restoring that object when the program is re-invoked, you're able to produce the effect of persistence. The reason it's called lightweight is that you can't simply define an object using some kind of persistent keyword and let the system take care of the details (although this might happen in the future). Instead, you must explicitly serialize and de-serialize the objects in your program. Object serialization was added to the language to support two major features. Java 1.1's remote method invocation (RMI) allows objects that live on other machines to behave as if they live on your machine. When sending messages to remote objects, object serialization is necessary to transport the arguments and return values. RMI is discussed in Chapter 15. Object serialization is also necessary for Java Beans, introduced in Java 1.1. When a Bean is used, its state information is generally configured at design time. This state information must be stored and later recovered when the program is started; object serialization performs this task. Serializing an object is quite simple, as long as the object implements the Serializable interface (this interface is just a flag and has no methods). In Java 1.1, many standard library classes have been changed so they're serializable, including all of the wrappers for the primitive types, all of the collection classes, and many others. Even Class objects can be serialized. At this point you need only call writeObject( ) and your object is serialized and sent to the OutputStream. To reverse the process, you wrap an InputStream inside an ObjectInputStream and call readObject( ). What comes back is, as usual, a handle to an upcast Object, so you must downcast to set things straight. This is sometimes referred to as the web of objects that a single object can be connected to, and it includes arrays of handles to objects as well as member objects. If you had to maintain your own object serialization scheme, maintaining the code to follow all these links would be a bit mind-boggling. However, Java object serialization seems to pull it off flawlessly, no doubt using an optimized algorithm that traverses the web of objects. When you create a Worm, you tell the constructor how long you want it to be. To make the next handle it calls the Worm constructor with a length of one less, etc. The final next handle is left as null, indicating the end of the Worm. The point of all this was to make something reasonably complex that couldn't easily be serialized. The act of serializing, however, is quite simple. Once the ObjectOutputStream is created from some other stream, writeObject( ) serializes the object. Notice the call to writeObject( ) for a String, as well. You can also write all the primitive data types using the same methods as DataOutputStream (they share the same interface). There are two separate try blocks that look similar. The first writes and reads a file and the second, for variety, writes and reads a ByteArray. You can read and write an object using serialization to any DataInputStream or DataOutputStream including, as you will see in the networking chapter, a network. Note that no constructor, not even the default constructor, is called in the process of deserializing a Serializable object. The entire object is restored by recovering data from the InputStream. Object serialization is another Java 1.1 feature that is not part of the new Reader and Writer hierarchies, but instead uses the old InputStream and OutputStream hierarchies. Thus you might encounter situations in which you're forced to mix the two hierarchies. Finding the class You might wonder what's necessary for an object to be recovered from its serialized state. For example, suppose you serialize an object and send it as a file or through a network to another machine. Could a program on the other machine reconstruct the object using only the contents of the file? The best way to answer this question is (as usual) by performing an experiment. You'll get a ClassNotFoundException. Controlling serialization As you can see, the default serialization mechanism is trivial to use. But what if you have special needs? Perhaps you have special security issues and you don't want to serialize portions of your object, or perhaps it just doesn't make sense for one sub-object to be serialized if that part needs to be created anew when the object is recovered. You can control the process of serialization by implementing the Externalizable interface instead of the Serializable interface. The following example shows simple implementations of the Externalizable interface methods. Can you see the difference between Blip1 and Blip2? The constructor for Blip1 is public, while the constructor for Blip2 is not, and that causes the exception upon recovery. When b1 is recovered, the Blip1 default constructor is called. This is different from recovering a Serializable object, in which the object is constructed entirely from its stored bits, with no constructor calls. With an Externalizable object, all the normal default construction behavior occurs (including the initializations at the point of field definition), and then readExternal( ) is called. You need to be aware of this - in particular the fact that all the default construction always takes place - to produce the correct behavior in your Externalizable objects. This means that if you don't initialize s and i in readExternal, it will be null (since the storage for the object gets wiped to zero in the first step of object creation). If you comment out the two lines of code following the phrases You must do this and run the program, you'll see that when the object is recovered, s is null and i is zero. If you are inheriting from an Externalizable object, you'll typically call the base-class versions of writeExternal( ) and readExternal( ) to provide proper storage and retrieval of the base-class components. This can be a bit confusing at first because the default construction behavior for an Externalizable object can make it seem like some kind of storage and retrieval takes place automatically. It does not. The transient keyword When you're controlling serialization, there might be a particular subobject that you don't want Java's serialization mechanism to automatically save and restore. This is commonly the case if that subobject represents sensitive information that you don't want to serialize, such as a password. Even if that information is private in the object, once it's serialized it's possible for someone to access it by reading a file or intercepting a network transmission. One way to prevent sensitive parts of your object from being serialized is to implement your class as Externalizable, as shown previously. Then nothing is automatically serialized and you can explicitly serialize only the necessary parts inside writeExternal( ). If you're working with a Serializable object, however, all serialization happens automatically. Suppose that, once you verify the login, you want to store the data, but without the password. The easiest way to do this is by implementing Serializable and marking the password field as transient. However, the password is transient, and so is not stored to disk; also the serialization mechanism makes no attempt to recover it. Note that toString( ) must check for a null value of password because if you try to assemble a String object using the overloaded '+' operator, and that operator encounters a null handle, you'll get a NullPointerException. Since Externalizable objects do not store any of their fields by default, the transient keyword is for use with Serializable objects only. An alternative to Externalizable If you're not keen on implementing the Externalizable interface, there's another approach. That is, if you provide these two methods they will be used instead of the default serialization. First of all, you might think that because these methods are not part of a base class or the Serializable interface, they ought to be defined in their own interface(s). But notice that they are defined as private, which means they are to be called only by other members of this class. In a word: confusing.) You might wonder how the ObjectOutputStream and ObjectInputStream objects have access to private methods of your class. We can only assume that this is part of the serialization magic. In any event, anything defined in an interface is automatically public so if writeObject( ) and readObject( ) must be private, then they can't be part of an interface. Since you must follow the signatures exactly, the effect is the same as if you're implementing an interface. It would appear that when you call ObjectOutputStream.writeObject( ), the Serializable object that you pass it to is interrogated (using reflection, no doubt) to see if it implements its own writeObject( ). If so, the normal serialization process is skipped and the writeObject( ) is called. The same sort of situation exists for readObject( ). There's one other twist. Inside your writeObject( ), you can choose to perform the default writeObject( ) action by calling defaultWriteObject( ). Likewise, inside readObject( ) you can call defaultReadObject( ). The fields are initialized inside the constructor rather than at the point of definition to prove that they are not being initialized by some automatic mechanism during deserialization. If you are going to use the default mechanism to write the non-transient parts of your object, you must call defaultWriteObject( ) as the first operation in writeObject( ) and defaultReadObject( ) as the first operation in readObject( ). These are strange method calls. Spooky. The storage and retrieval of the transient objects uses more familiar code. And yet, think about what happens here. In main( ), a SerialCtl object is created, and then it's serialized to an ObjectOutputStream. A similar approach holds true for readObject( ). Perhaps this was the only practical way that they could solve the problem, but it's certainly strange. Versioning It's possible that you might want to change the version of a serializable class (objects of the original class might be stored in a database, for example). This is supported but you'll probably do it only in special cases, and it requires an extra depth of understanding that we will not attempt to achieve here. The JDK1.1 HTML documents downloadable from Sun (which might be part of your Java package's online documents) cover this topic quite thoroughly. Using persistence It's quite appealing to use serialization technology to store some of the state of your program so that you can easily restore the program to the current state later. But before you can do this, some questions must be answered. What happens if you serialize two objects that both have a handle to a third object? When you restore those two objects from their serialized state, do you get only one occurrence of the third object? What if you serialize your two objects to separate files and deserialize them in different parts of your code? Animal objects contain fields of type House. In main( ), a Vector of these Animals is created and it is serialized twice to one stream and then again to a separate stream. But notice that in animals1 and animals2 the same addresses appear, including the references to the House object that both share. On the other hand, when animals3 is recovered the system has no way of knowing that the objects in this other stream are aliases of the objects in the first stream, so it makes a completely different web of objects. As long as you're serializing everything to a single stream, you'll be able to recover the same web of objects that you wrote, with no accidental duplication of objects. The safest thing to do if you want to save the state of a system is to serialize as an atomic operation. If you serialize some things, do some other work, and serialize some more, etc., then you will not be storing the system safely. Instead, put all the objects that comprise the state of your system in a single collection and simply write that collection out in one operation. Then you can restore it with a single method call as well. The following example is an imaginary computer-aided design (CAD) system that demonstrates the approach. In addition, it throws in the issue of static fields - if you look at the documentation you'll see that Class is Serializable, so it should be easy to store the static fields by simply serializing the Class object. That seems like a sensible approach, anyway. Each Shape contains data, and each derived Shape class contains a static field that determines the color of all of those types of Shapes. The randomFactory( ) method creates a different Shape each time you call it, using random values for the Shape data. Circle and Square are straightforward extensions of Shape; the only difference is that Circle initializes color at the point of definition and Square initializes it in the constructor. We'll leave the discussion of Line for later. In main( ), one Vector is used to hold the Class objects and the other to hold the shapes. If you don't provide a command line argument the shapeTypes Vector is created and the Class objects are added, and then the shapes Vector is created and Shape objects are added. Next, all the static color values are set to GREEN, and everything is serialized to the file CADState.out. If you provide a command line argument (presumably CADState.out), that file is opened and used to restore the state of the program. In both situations, the resulting Vector of Shapes is printed out. It's all '3' going in, but it doesn't come out that way. Circles have a value of 1 (RED, which is the definition), and Squares have a value of 0 (remember, they are initialized in the constructor). It's as if the statics didn't get serialized at all! That's right - even though class Class is Serializable, it doesn't do what you expect. So if you want to serialize statics, you must do it yourself. This is what the serializeStaticState( ) and deserializeStaticState( ) static methods in Line are for. You can see that they are explicitly called as part of the storage and retrieval process. Another issue you might have to think about is security, since serialization also saves private data. If you have a security issue, those fields should be marked as transient. But then you have to design a secure way to store that information so that when you do a restore you can reset those private variables. Summary The Java IO stream library does seem to satisfy the basic requirements: you can perform reading and writing with the console, a file, a block of memory, or even across the Internet (as you will see in Chapter 15). It's possible (by inheriting from InputStream and OutputStream) to create new types of input and output objects. There are questions left unanswered by the documentation and design of the IO stream library. In Java, it appears that you are supposed to use a File object to determine whether a file exists, because if you open it as an FileOutputStream or FileWriter it will always get overwritten. By representing both files and directory paths, the File class also suggests poor design by violating the maxim Don't try to do too much in a single class. The IO stream library brings up mixed feelings. It does much of the job and it's portable. But if you don't already understand the decorator pattern, the design is non-intuitive, so there's extra overhead in learning and teaching it. It's also incomplete: there's no support for the kind of output formatting that almost every other language's IO package supports. Exercises 1. Open a text file so that you can read the file one line at a time. Read each line as a String and place that String object into a Vector. Print out all of the lines in the Vector in reverse order. 2. Modify Exercise 1 so that the name of the file you read is provided as a command-line argument. 3. Modify Exercise 2 to also open a text file so you can write text into it. Write the lines in the Vector, along with line numbers, out to the file. 4. Modify Exercise 2 to force all the lines in the Vector to upper case and send the results to System.out. 5. Modify Exercise 2 to take additional arguments of words to find in the file. Print out any lines in which the words match. 6. In Blips.java, copy the file and rename it to BlipCheck.java and rename the class Blip2 to BlipCheck (making it public in the process). Next, comment out the default constructor for BlipCheck. Run it and explain why it works. 7. In Blip3.java, comment out the two lines after the phrases You must do this: and run the program. Explain the result and why it differs from when the two lines are in the program. 8. Convert the SortedWordCount.java program to use the Java 1.1 IO Streams. 9. Repair the program CADState.java as described in the text. In GreenhouseControls.java, the Restart( ) inner class has a hard-coded set of events. Change the program so that it reads the events and their relative times from a text file. However, the need for RTTI uncovers a whole plethora of interesting (and often perplexing) OO design issues and raises fundamental questions of how you should structure your programs. This chapter looks at the ways that Java allows you to discover information about objects and classes at run-time. This takes two forms: traditional RTTI, which assumes that you have all the types available at compile-time and run-time, and the reflection mechanism in Java 1.1, which allows you to discover class information solely at run-time. The traditional RTTI will be covered first, followed by a discussion of reflection. The need for RTTI Consider the now familiar example of a class hierarchy that uses polymorphism. The generic type is the base class Shape, and the specific derived types are Circle, Square, and Triangle: This is a typical class hierarchy diagram, with the base class at the top and the derived classes growing downward. In this example, the dynamically bound method in the Shape interface is draw( ), so the intent is for the client programmer to call draw( ) through a generic Shape handle. That's polymorphism. Thus, you generally create a specific object (Circle, Square, or Triangle), upcast it to a Shape (forgetting the specific type of the object), and use that anonymous Shape handle in the rest of the program. Since Shape has no concrete members (that is, members with definitions), and it's not intended that you ever create a plain Shape object, the most appropriate and flexible representation is an interface. It's also cleaner because you don't have all those abstract keywords lying about. Each of the derived classes overrides the base-class draw method so it behaves differently. In main( ), specific types of Shape are created and then added to a Vector. This is the point at which the upcast occurs because the Vector holds only Objects. Since everything in Java (with the exception of primitives) is an Object, a Vector can also hold Shape objects. But during an upcast to Object, it also loses any specific information, including the fact that the objects are shapes. To the Vector, they are just Objects. At the point you fetch an element out of the Vector with nextElement( ), things get a little busy. Since Vector holds only Objects, nextElement( ) naturally produces an Object handle. But we know it's really a Shape handle, and we want to send Shape messages to that object. So a cast to Shape is necessary using the traditional (Shape) cast. This is the most basic form of RTTI, since in Java all casts are checked at run-time for correctness. That's exactly what RTTI means: at run-time, the type of an object is identified. In this case, the RTTI cast is only partial: the Object is cast to a Shape, and not all the way to a Circle, Square, or Triangle. That's because the only thing we know at this point is that the Vector is full of Shapes. At compile-time, this is enforced only by your own self-imposed rules, but at run-time the cast ensures it. Now polymorphism takes over and the exact method that's called for the Shape is determined by whether the handle is for a Circle, Square, or Triangle. And in general, this is how it should be; you want the bulk of your code to know as little as possible about specific types of objects, and to just deal with the general representation of a family of objects (in this case, Shape). As a result, your code will be easier to write, read, and maintain, and your designs will be easier to implement, understand, and change. So polymorphism is the general goal in object-oriented programming. But what if you have a special programming problem that's easiest to solve if you know the exact type of a generic handle? For example, suppose you want to allow your users to highlight all the shapes of any particular type by turning them purple. This way, they can find all the triangles on the screen by highlighting them. This is what RTTI accomplishes: you can ask a handle to a Shape exactly what type it's referring to. The Class object To understand how RTTI works in Java, you must first know how type information is represented at run time. This is accomplished through a special kind of object called the Class object, which contains information about the class. There's a Class object for each class that is part of your program. That is, each time you write a new class, a single Class object is also created (and stored, appropriately enough, in an identically named.class file). At run time, when you want to make an object of that class, the Java Virtual Machine (JVM) that's executing your program first checks to see if the Class object for that type is loaded. If not, the JVM loads it by finding the .class file with that name. Thus, a Java program isn't completely loaded before it begins, which is different from many traditional languages. Once the Class object for that type is in memory, it is used to create all objects of that type. Information will be printed out to tell you when loading occurs for that class. In main( ), the object creations are spread out between print statements to help detect the time of loading. A particularly interesting line is: Class.forName(Gum); This method is a static member of Class (to which all Class objects belong). A Class object is like any other object and so you can get and manipulate a handle to it. It returns a Class handle. While this JVM produces the desired effect because it does get the classes loaded before they're needed, it's uncertain whether the behavior shown is precisely correct. Class literals In Java 1.1 you have a second way to produce the handle to the Class object: use the class literal. In the above program this would look like: Gum.class; which is not only simpler, but also safer since it's checked at compile time. Because it eliminates the method call, it's also more efficient. Class literals work with regular classes as well as interfaces, arrays, and primitive types. In addition, there's a standard field called TYPE that exists for each of the primitive wrapper classes. 2. The Class object representing the type of your object. The Class object can be queried for useful runtime information. In C++, the classic cast (Shape) does not perform RTTI. It simply tells the compiler to treat the object as the new type. In Java, which does perform the type check, this cast is often called a type safe downcast. The reason for the term downcast is the historical arrangement of the class hierarchy diagram. If casting a Circle to a Shape is an upcast, then casting a Shape to a Circle is a downcast. There's a third form of RTTI in Java. This is the keyword instanceof that tells you if an object is an instance of a particular type. It returns a boolean so you use it in the form of a question, like this: if(x instanceof Dog) ((Dog)x).bark(); The above if statement checks to see if the object x belongs to the class Dog before casting x to a Dog. It's important to use instanceof before a downcast when you don't have other information that tells you the type of the object; otherwise you'll end up with a ClassCastException. Ordinarily, you might be hunting for one type (triangles to turn purple, for example), but the following program shows how to tally all of the objects using instanceof. In the example above you might feel that it's tedious to write out all of those instanceof expressions, and you're right. But in Java 1.0 there is no way to cleverly automate it by creating a Vector of Class objects and comparing it to those instead. This isn't as great a restriction as you might think, because you'll eventually understand that your design is probably flawed if you end up writing a lot of instanceof expressions. Of course this example is contrived - you'd probably put a static data member in each type and increment it in the constructor to keep track of the counts. You would do something like that if you had control of the source code for the class and could change it. Since this is not always the case, RTTI can come in handy. Using class literals It's interesting to see how the PetCount.java example can be rewritten using Java 1.1 class literals. Notice the extra work for this: the class name is not, for example, Gerbil, but instead c11.petcount2.Gerbil since the package name is included. Notice also that the system can distinguish between classes and interfaces. You can also see that the creation of petTypes does not need to be surrounded by a try block since it's evaluated at compile time and thus won't throw any exceptions, unlike Class.forName( ). When the Pet objects are dynamically created, you can see that the random number is restricted so it is between 1 and petTypes.length and does not include zero. That's because zero refers to Pet.class, and presumably a generic Pet object is not interesting. However, since Pet.class is part of petTypes the result is that all of the pets get counted. A dynamic instanceof Java 1.1 has added the isInstance method to the class Class. This allows you to dynamically call the instanceof operator, which you could do only statically in Java 1.0 (as previously shown). In addition, this means that you can add new types of pets simply by changing the petTypes array; the rest of the program does not need modification (as it did when using the instanceof expressions). RTTI syntax Java performs its RTTI using the Class object, even if you're doing something like a cast. The class Class also has a number of other ways you can use RTTI. First, you must get a handle to the appropriate Class object. One way to do this, as shown in the previous example, is to use a string and the Class.forName( ) method. This is convenient because you don't need an object of that type in order to get the Class handle. However, if you do already have an object of the type you're interested in, you can fetch the Class handle by calling a method that's part of the Object root class: getClass( ). This returns the Class handle representing the actual type of the object. In main( ), a Class handle is created and initialized to the FancyToy Class using forName( ) inside an appropriate try block. The Class.getInterfaces( ) method returns an array of Class objects representing the interfaces that are contained in the Class object of interest. If you have a Class object you can also ask it for its direct base class using getSuperclass( ). This, of course, returns a Class handle that you can further query. This means that, at run time, you can discover an object's entire class hierarchy. The newInstance( ) method of Class can, at first, seem like just another way to clone( ) an object. However, you can create a new object with newInstance( ) without an existing object, as seen here, because there is no Toy object, only cy, which is a handle to y's Class object. And when you create a new instance, you get back an Object handle. But that handle is pointing to a Toy object. Of course, before you can send any messages other than those accepted by Object, you have to investigate it a bit and do some casting. In addition, the class that's being created with newInstance( ) must have a default constructor. There's no way to use newInstance( ) to create objects that have non-default constructors, so this can be a bit limiting in Java 1. However, the reflection API in Java 1.1 (discussed in the next section) allows you to dynamically use any constructor in a class. The final method in the listing is printInfo( ), which takes a Class handle and gets its name with getName( ), and finds out whether it's an interface with isInterface( ). The output from this program is: Class name: FancyToy is interface? Reflection: run-time class information If you don't know the precise type of an object, RTTI will tell you. However, there's a limitation: the type must be known at compile time in order for you to be able to detect it using RTTI and do something useful with the information. Put another way, the compiler must know about all the classes you're working with for RTTI. This doesn't seem like that much of a limitation at first, but suppose you're given a handle to an object that's not in your program space. In fact, the class of the object isn't even available to your program at compile time. For example, suppose you get a bunch of bytes from a disk file or from a network connection and you're told that those bytes represent a class. Since the compiler can't know about the class while it's compiling the code, how can you possibly use such a class? In a traditional programming environment this seems like a far-fetched scenario. But as we move into a larger programming world there are important cases in which this happens. The first is component-based programming in which you build projects using Rapid Application Development (RAD) in an application builder tool. This is a visual approach to creating a program (which you see on the screen as a form) by moving icons that represent components onto the form. These components are then configured by setting some of their values at program time. This design-time configuration requires that any component be instantiable and that it expose some part of itself and allow its values to be read and set. In addition, components that handle GUI events must expose information about appropriate methods so that the RAD environment can assist the programmer in overriding these event-handling methods. Reflection provides the mechanism to detect the available methods and produce the method names. Java 1.1 provides a structure for component-based programming through Java Beans (described in Chapter 13). Another compelling motivation for discovering class information at run-time is to provide the ability to create and execute objects on remote platforms across a network. This is called Remote Method Invocation (RMI) and it allows a Java program (version 1.1 and higher) to have objects distributed across many machines. This distribution can happen for a number of reasons: perhaps you're doing a computation-intensive task and you want to break it up and put pieces on machines that are idle in order to speed things up. Objects of these types are created by the JVM at run-time to represent the corresponding member in the unknown class. You can then use the Constructors to create new objects, the get( ) and set( ) methods to read and modify the fields associated with Field objects, and the invoke( ) method to call a method associated with a Method object. In addition, you can call the convenience methods getFields( ), getMethods( ), getConstructors( ), etc., to return arrays of the objects representing the fields, methods, and constructors. It's important to realize that there's nothing magic about reflection. Thus, the.class file for that particular type must still be available to the JVM, either on the local machine or across the network. So the true difference between RTTI and reflection is that with RTTI, the compiler opens and examines the .class file at compile time. Put another way, you can call all the methods of an object in the normal way. With reflection, the .class file is unavailable at compile time; it is opened and examined by the run-time environment. However, there are times when it's quite useful to be able to dynamically extract information about a class. One extremely useful tool is a class method extractor. As mentioned before, looking at a class definition source code or online documentation shows only the methods that are defined or overridden within that class definition. But there could be dozens more available to you that have come from base classes. To locate these is both tedious and time consuming. Fortunately, reflection provides a way to write a simple tool that will automatically show you the entire interface. Each of these classes has further methods to dissect the names, arguments, and return values of the methods they represent. But you can also just use toString( ), as is done here, to produce a String with the entire method signature. The rest of the code is just for extracting command line information, determining if a particular signature matches with your target string (using indexOf( )), and printing the results. This shows reflection in action, since the result produced by Class.forName( ) cannot be known at compile-time, and therefore all the method signature information is being extracted at run-time. If you investigate your online documentation on reflection, you'll see that there is enough support to actually set up and make a method call on an object that's totally unknown at compile-time. Again, this is something you'll probably never need to do yourself - the support is there for Java and so a programming environment can manipulate Java Beans - but it's interesting. An interesting experiment is to run java ShowMethods ShowMethods. This produces a listing that includes a public default constructor, even though you can see from the code that no constructor was defined. The constructor you see is the one that's automatically synthesized by the compiler. If you then make ShowMethods a non-public class (that is, friendly), the synthesized default constructor no longer shows up in the output. The synthesized default constructor is automatically given the same access as the class. The output for ShowMethods is still a little tedious. Each of these String objects is then passed through StripQualifiers.Strip( ) to remove all the method qualification. As you can see, this uses the StreamTokenizer and String manipulation to do its work. Chapter 17 contains a GUI version of this program so you can leave it running while you're writing code, to allow quick lookups. Summary RTTI allows you to discover type information from an anonymous base-class handle. Thus, it's ripe for misuse by the novice since it might make sense before polymorphic method calls do. For many people coming from a procedural background, it's difficult not to organize their programs into sets of switch statements. They could accomplish this with RTTI and thus lose the important value of polymorphism in code development and maintenance. The intent of Java is that you use polymorphic method calls throughout your code, and you use RTTI only when you must. If the base class comes from a library or is otherwise controlled by someone else, a solution to the problem is RTTI: You can inherit a new type and add your extra method. Elsewhere in the code you can detect your particular type and call that special method. This doesn't destroy the polymorphism and extensibility of the program because adding a new type will not require you to hunt for switch statements in your program. However, when you add new code in your main body that requires your new feature, you must use RTTI to detect your particular type. Putting a feature in a base class might mean that, for the benefit of one particular class, all of the other classes derived from that base require some meaningless stub of a method. This makes the interface less clear and annoys those who must override abstract methods when they derive from that base class. For example, consider a class hierarchy representing musical instruments. Suppose you wanted to clear the spit valves of all the appropriate instruments in your orchestra. One option is to put a ClearSpitValve( ) method in the base class Instrument, but this is confusing because it implies that Percussion and Electronic instruments also have spit valves. RTTI provides a much more reasonable solution in this case because you can place the method in the specific class (Wind in this case), where it's appropriate. However, a more appropriate solution is to put a prepareInstrument( ) method in the base class, but you might not see this when you're first solving the problem and could mistakenly assume that you must use RTTI. Finally, RTTI will sometimes solve efficiency problems. Exercises 1. Write a method that takes an object and recursively prints all the classes in that object's hierarchy. 2. In ToyTest.java, comment out Toy's default constructor and explain what happens. 3. Create a new type of collection that uses a Vector. Capture the type of the first object you put in it, and then allow the user to insert objects of only that type from then on. 4. Write a program to determine whether an array of char is a primitive type or a true object. 5. Implement clearSpitValve( ) as described in this chapter. 6. Implement the rotate(Shape) method described in this chapter, such that it checks to see if it is rotating a Circle (and, if so, doesn't perform the operation). In many programming languages, if not all of them, you can use that language's regular way to pass objects around and most of the time everything works fine. But it always seems that there comes a point at which you must do something irregular and suddenly things get a bit more complicated (or in the case of C++, quite complicated). Java is no exception, and it's important that you understand exactly what's happening with them as you pass them around and assign to them. This chapter will provide that insight. Or to put in another way, Java has pointers, but no pointer arithmetic. Passing handles around When you pass a handle into a method, you're still pointing to the same object. Thus, Object's version of toString( ) is used, which prints out the class of the object followed by the address where that object is located (not the handle, but the actual object storage). The output looks like this: p inside main(): PassHandles@1653748 h inside f(): PassHandles@1653748 You can see that both p and h refer to the same object. This is far more efficient than duplicating a new PassHandles object just so that you can send an argument to a method. But it brings up an important issue. Aliasing Aliasing means that more than one handle is tied to the same object, as in the above example. The problem with aliasing occurs when someone writes to that object. If the owners of the other handles aren't expecting that object to change, they'll be surprised. So the contents of handle x, which is the address of the object x is pointing to, is assigned to y, and thus both x and y are attached to the same object. So when x's i is incremented in the statement: x.i++; y's i will be affected as well. This can be seen in the output: x: 7 y: 7 Incrementing x x: 8 y: 8 One good solution in this case is to simply not do it: don't consciously alias more than one handle to an object at the same scope. Your code will be much easier to understand and debug. When this kind of situation arises, you must decide whether it makes sense, whether the user expects it, and whether it's going to cause problems. Because of the confusion and pitfalls, it's much better to avoid changing the argument. If you need to modify an argument during a method call and you don't intend to modify the outside argument, then you should protect that argument by making a copy inside your method. That's the subject of much of this chapter. Making local copies To review: all argument passing in Java is performed by passing handles. That is, when you pass an object, you're really passing only a handle to an object that lives outside the method, so if you perform any modifications with that handle, you modify the outside object. In addition: * Aliasing happens automatically during argument passing. If you're only reading information from an object and not modifying it, passing a handle is the most efficient form of argument passing. This is nice; the default way of doing things is also the most efficient. However, sometimes it's necessary to be able to treat the object as if it were local so that changes you make affect only a local copy and do not modify the outside object. Many programming languages support the ability to automatically make a local copy of the outside object, inside the method.1 Java does not, but it allows you to produce this effect. Pass by value This brings up the terminology issue, which always seems good for an argument. The term is pass by value, and the meaning depends on how you perceive the operation of the program. The general meaning is that you get a local copy of whatever you're passing, but the real question is how you think about what you're passing. When it comes to the meaning of pass by value, there are two fairly distinct camps: 1. Java passes everything by value. When you're passing primitives into a method, you get a distinct copy of the primitive. When you're passing a handle into a method, you get a copy of the handle. Ergo, everything is pass by value. Of course, the assumption is that you're always thinking (and caring) that handles are being passed, but it seems like the Java design has gone a long way toward allowing you to ignore (most of the time) that you're working with a handle. That is, it seems to allow you to think of the handle as the object, since it implicitly dereferences it whenever you make a method call. 2. Java passes primitives by value (no argument there), but objects are passed by reference. There appears to be some support for this view within Sun, since one of the reserved but not implemented keywords is byvalue. In the end, it isn't that important - what is important is that you understand that passing a handle allows the caller's object to be changed unexpectedly. Cloning objects The most likely reason for making a local copy of an object is if you're going to modify that object and you don't want to modify the caller's object. If you decide that you want to make a local copy, you simply use the clone( ) method to perform the operation. This is a method that's defined as protected in the base class Object and which you must override as public in any derived classes that you want to clone. This example shows how Vector's clone( ) method does not automatically try to clone each of the objects that the Vector contains - the old Vector and the cloned Vector are aliased to the same objects. This is often called a shallow copy, since it's copying only the surface portion of an object. The actual object consists of this surface plus all the objects that the handles are pointing to, plus all the objects those objects are pointing to, etc. This is often referred to as the web of objects. Copying the entire mess is called a deep copy. Cloning in Java goes against this idea; if you want it to exist for a class, you must specifically add code to make cloning work. Using a trick with protected To prevent default clonability in every class you create, the clone( ) method is protected in the base class Object. Not only does this mean that it's not available by default to the client programmer who is simply using the class (not subclassing it), but it also means that you cannot call clone( ) via a handle to the base class. Thus, if you say: Integer x = new Integer(1); x = x.clone(); You will get, at compile time, an error message that says clone( ) is not accessible (since Integer doesn't override it and it defaults to the protected version). If, however, you're in a class derived from Object (as all classes are), then you have permission to call Object.clone( ) because it's protected and you're an inheritor. The base class clone( ) has useful functionality - it performs the actual bitwise duplication of the derived-class object, thus acting as the common cloning operation. However, you then need to make your clone operation public for it to be accessible. So two key issues when you clone are: virtually always call super.clone( ) and make your clone public. The protected trick works only once, the first time you inherit from a class that has no clonability and you want to make a class that's cloneable. In any classes inherited from your class the clone( ) method is available since it's not possible in Java to reduce the access of a method during derivation. That is, once a class is cloneable, everything derived from it is cloneable unless you use provided mechanisms (described later) to turn off cloning. Implementing the Cloneable interface There's one more thing you need to do to complete the clonability of an object: implement the Cloneable interface. This interface is a bit strange because it's empty! The use of interface here is considered by some to be a hack because it's using a feature for something other than its original intent. Implementing the Cloneable interface acts as a kind of a flag, wired into the type of the class. There are two reasons for the existence of the Cloneable interface. First, you might have an upcast handle to a base type and not know whether it's possible to clone that object. So Object.clone( ) verifies that a class implements the Cloneable interface. If not, it throws a CloneNotSupportedException exception. So in general, you're forced to implement Cloneable as part of support for cloning. Second, for the initial part of your clone( ) operation you should call the base-class version of clone( ). The clone( ) that's being called here is the one that's predefined inside Object, and you can call it because it's protected and thereby accessible in derived classes. Object.clone( ) figures out how big the object is, creates enough memory for a new one, and copies all the bits from the old to the new. This is called a bitwise copy, and is typically what you'd expect a clone( ) method to do. But before Object.clone( ) performs its operations, it first checks to see if a class is Cloneable, that is, whether it implements the Cloneable interface. If it doesn't, Object.clone( ) throws a CloneNotSupportedException to indicate that you can't clone it. Thus, you've got to surround your call to super.clone( ) with a try-catch block, to catch an exception that should never happen (because you've implemented the Cloneable interface). In LocalCopy, the two methods g( ) and f( ) demonstrate the difference between the two approaches for argument passing. It can then proceed to do whatever it wants, and even to return a handle to this new object without any ill effects to the original. Notice the somewhat curious-looking statement: v = (MyObject)v.clone(); This is where the local copy is created. To prevent confusion by such a statement, remember that this rather strange coding idiom is perfectly feasible in Java because everything that has a name is actually a handle. So the handle v is used to clone( ) a copy of what it refers to, and this returns a handle to the base type Object (because it's defined that way in Object.clone( )) that must then be cast to the proper type. In main( ), the difference between the effects of the two different argument-passing approaches in the two different methods is tested. The output is: a == b a = 12 b = 12 c!= d c = 47 d = 48 It's important to notice that the equivalence tests in Java do not look inside the objects being compared to see if their values are the same. The == and!= operators are simply comparing the contents of the handles. If the addresses inside the handles are the same, the handles are pointing to the same object and are therefore equal. So what the operators are really testing is whether the handles are aliased to the same object! The effect of Object.clone( ) What actually happens when Object.clone( ) is called that makes it so essential to call super.clone( ) when you override clone( ) in your class? The clone( ) method in the root class is responsible for creating the correct amount of storage and making the bitwise copy of the bits from the original object into the new object's storage. That is, it doesn't just make storage and copy an Object - it actually figures out the size of the precise object that's being copied and duplicates that. Since all this is happening from the code in the clone( ) method defined in the root class (that has no idea what's being inherited from it), you can guess that the process involves RTTI to determine the actual object that's being cloned. This way, the clone( ) method can create the proper amount of storage and do the correct bitcopy for that type. Whatever you do, the first part of the cloning process should normally be a call to super.clone( ). This establishes the groundwork for the cloning operation by making an exact duplicate. At this point you can perform other operations necessary to complete the cloning. To know for sure what those other operations are, you need to understand exactly what Object.clone( ) buys you. In particular, does it automatically clone the destination of all the handles? Thus, it's a singly-linked list. The segments are created recursively, decrementing the first constructor argument for each segment until zero is reached. To give each segment a unique tag, the second argument, a char, is incremented for each recursive constructor call. The increment( ) method recursively increments each tag so you can see the change, and the toString( ) recursively prints each tag. The output is: s = :a:b:c:d:e s2 = :a:b:c:d:e after s.increment, s2 = :a:c:d:e:f This means that only the first segment is duplicated by Object.clone( ), so it does a shallow copy. If you want the whole snake to be duplicated - a deep copy - you must perform the additional operations inside your overridden clone( ). You'll typically call super.clone( ) in any class derived from a cloneable class to make sure that all of the base-class operations (including Object.clone( )) take place. This is followed by an explicit call to clone( ) for every handle in your object; otherwise those handles will be aliased to those of the original object. It's analogous to the way constructors are called - base-class constructor first, then the next-derived constructor, and so on to the most-derived constructor. The difference is that clone( ) is not a constructor so there's nothing to make it happen automatically. You must make sure to do it yourself. Cloning a composed object There's a problem you'll encounter when trying to deep copy a composed object. You must assume that the clone( ) method in the member objects will in turn perform a deep copy on their handles, and so on. This is quite a commitment. Therefore, the clone( ) method can be quite simple: it calls super.clone( ) and returns the result. Note that the clone( ) code for both classes is identical. OceanReading is composed of DepthReading and TemperatureReading objects and so, to produce a deep copy, its clone( ) must clone the handles inside OceanReading. To accomplish this, the result of super.clone( ) must be cast to an OceanReading object (so you can access the depth and temperature handles). A deep copy with Vector Let's revisit the Vector example from earlier in this chapter. You might think that you'd need to override clone( ) again to make sure j is copied, but that's not the case. When Int2's clone( ) is called as Int3's clone( ), it calls Object.clone( ), which determines that it's working with an Int3 and duplicates all the bits in the Int3. As long as you don't add handles that need to be cloned, the one call to Object.clone( ) performs all of the necessary duplication, regardless of how far down in the hierarchy clone( ) is defined. You can see what's necessary in order to do a deep copy of a Vector: after the Vector is cloned, you have to step through and clone each one of the objects pointed to by the Vector. You'd have to do something similar to this to do a deep copy of a Hashtable. The remainder of the example shows that the cloning did happen by showing that, once an object is cloned, you can change it and the original object is left untouched. Deep copy via serialization When you consider Java 1.1 object serialization (introduced in Chapter 10), you might observe that an object that's serialized and then deserialized is, in effect, cloned. So why not use serialization to perform deep copying? It's interesting to notice that while Serializable classes are easy to set up, there's much more work going on to duplicate them. Cloning involves a lot of work to set up the class, but the actual duplication of objects is relatively simple. The results really tell the tale. Adding cloneability further down a hierarchy If you create a new class, its base class defaults to Object, which defaults to non-clonability (as you'll see in the next section). As long as you don't explicitly add clonability, you won't get it. When clonability is added in Scientist, then Scientist and all its descendants are cloneable. Why this strange design? If all this seems to be a strange scheme, that's because it is. You might wonder why it worked out this way. What is the meaning behind this design? What follows is not a substantiated story - probably because much of the marketing around Java makes it out to be a perfectly-designed language - but it does go a long way toward explaining how things ended up the way they did. Originally, Java was designed as a language to control hardware boxes, and definitely not with the Internet in mind. In a general-purpose language like this, it makes sense that the programmer be able to clone any object. Thus, clone( ) was placed in the root class Object, but it was a public method so you could always clone any object. This seemed to be the most flexible approach, and after all, what could it hurt? Well, when Java was seen as the ultimate Internet programming language, things changed. Suddenly, there are security issues, and of course, these issues are dealt with using objects, and you don't necessarily want anyone to be able to clone your security objects. So what you're seeing is a lot of patches applied on the original simple and straightforward scheme: clone( ) is now protected in Object. You must override it and implement Cloneable and deal with the exceptions. It's worth noting that you must use the Cloneable interface only if you're going to call Object's clone( ), method, since that method checks at run-time to make sure that your class implements Cloneable. But for consistency (and since Cloneable is empty anyway) you should implement it. Controlling cloneability You might suggest that, to remove clonability, the clone( ) method simply be made private, but this won't work since you cannot take a base-class method and make it more private in a derived class. So it's not that simple. And yet, it's necessary to be able to control whether an object can be cloned. There are actually a number of attitudes you can take to this in a class that you design: 1. Indifference. You don't do anything about cloning, which means that your class can't be cloned but a class that inherits from you can add cloning if it wants. This works only if the default Object.clone( ) will do something reasonable with all the fields in your class. 2. Support clone( ). Follow the standard practice of implementing Cloneable and overriding clone( ). In the overridden clone( ), you call super.clone( ) and catch all exceptions (so your overridden clone( ) doesn't throw any exceptions). 3. Support cloning conditionally. For example, consider a special sort of Vector that tries to clone all the objects it holds. When you write such a Vector, you don't know what sort of objects the client programmer might put into your Vector, so you don't know whether they can be cloned. 4. Don't implement Cloneable but override clone( ) as protected, producing the correct copying behavior for any fields. This way, anyone inheriting from this class can override clone( ) and call super.clone( ) to produce the correct copying behavior. Note that your implementation can and should invoke super.clone( ) even though that method expects a Cloneable object (it will throw an exception otherwise), because no one will directly invoke it on an object of your type. It will get invoked only through a derived class, which, if it is to work successfully, implements Cloneable. 5. Try to prevent cloning by not implementing Cloneable and overriding clone( ) to throw an exception. This is successful only if any class derived from this calls super.clone( ) in its redefinition of clone( ). Otherwise, a programmer may be able to get around it. 6. Prevent cloning by making your class final. If clone( ) has not been overridden by any of your ancestor classes, then it can't be. If it has, then override it again and throw CloneNotSupportedException. Making the class final is the only way to guarantee that cloning is prevented. In addition, when dealing with security objects or other situations in which you want to control the number of objects created you should make all constructors private and provide one or more special methods for creating objects. That way, these methods can restrict the number of objects created and the conditions in which they're created. But if you have a handle to an Ordinary object that might have been upcast from a more derived class, you can't tell if it can be cloned or not. The class WrongClone shows an incorrect way to implement cloning. In IsCloneable you can see all the right actions performed for cloning: clone( ) is overridden and Cloneable is implemented. However, this clone( ) method and several others that follow in this example do not catch CloneNotSupportedException, but instead pass it through to the caller, who must then put a try-catch block around it. In your own clone( ) methods you will typically catch CloneNotSupportedException inside clone( ) rather than passing it through. As you'll see, in this example it's more informative to pass the exceptions through. Class NoMore attempts to turn off cloning in the way that the Java designers intended: in the derived class clone( ), you throw CloneNotSupportedException. The clone( ) method in class TryMore properly calls super.clone( ), and this resolves to NoMore.clone( ), which throws an exception and prevents cloning. But what if the programmer doesn't follow the proper path of calling super.clone( ) inside the overridden clone( ) method? In BackOn, you can see how this can happen. This class uses a separate method duplicate( ) to make a copy of the current object and calls this method inside clone( ) instead of calling super.clone( ). The exception is never thrown and the new class is cloneable. You can't rely on throwing an exception to prevent making a cloneable class. The only sure-fire solution is shown in ReallyNoMore, which is final and thus cannot be inherited. That means if clone( ) throws an exception in the final class, it cannot be modified with inheritance and the prevention of cloning is assured. The first method you see in class CheckCloneable is tryToClone( ), which takes any Ordinary object and checks to see whether it's cloneable with instanceof. If so, it casts the object to an IsCloneable, calls clone( ) and casts the result back to Ordinary, catching any exceptions that are thrown. Notice the use of run-time type identification (see Chapter 11) to print out the class name so you can see what's happening. In main( ), different types of Ordinary objects are created and upcast to Ordinary in the array definition. The first two lines of code after that create a plain Ordinary object and try to clone it. However, this code will not compile because clone( ) is a protected method in Object. The remainder of the code steps through the array and tries to clone each object, reporting the success or failure of each. 2. Override clone( ). 3. Call super.clone( ) inside your clone( ). 4. Capture exceptions inside your clone( ). This will produce the most convenient effects. The copy-constructor Cloning can seem to be a complicated process to set up. It might seem like there should be an alternative. One approach that might occur to you (especially if you're a C++ programmer) is to make a special constructor whose job it is to duplicate an object. In C++, this is called the copy constructor. At first, this seems like the obvious solution. Sure, fruit has qualities, but why not just put data members representing those qualities directly into the Fruit class? There are two potential reasons. The first is that you might want to easily insert or change the qualities. Note that Fruit has a protected addQualities( ) method to allow derived classes to do this. The second reason for making FruitQualities a separate object is in case you want to add new qualities or to change the behavior via inheritance and polymorphism. Of course, when GreenZebra uses the FruitQualities it must downcast it to the correct type (as seen in evaluate( )), but it always knows that type is ZebraQualities. You'll also see that there's a Seed class, and that Fruit (which by definition carries its own seeds) contains an array of Seeds. Finally, notice that each class has a copy constructor, and that each copy constructor must take care to call the copy constructors for the base class and member objects to produce a deep copy. The copy constructor is tested inside the class CopyConstructor. Here's the output: In ripen, t is a Tomato In slice, f is a Fruit In ripen, t is a Tomato In slice, f is a Fruit This is where the problem shows up. After the copy-construction that happens to the Tomato inside slice( ), the result is no longer a Tomato object, but just a Fruit. It has lost all of its tomato-ness. Further, when you take a GreenZebra, both ripen( ) and slice( ) turn it into a Tomato and a Fruit, respectively. Thus, unfortunately, the copy constructor scheme is no good to us in Java when attempting to make a local copy of an object. Why does it work in C++ and not Java? The copy constructor is a fundamental part of C++, since it automatically makes a local copy of an object. Yet the example above proves that it does not work for Java. In Java everything that we manipulate is a handle, while in C++ you can have handle-like entities and you can also pass around the objects directly. That's what the C++ copy constructor is for: when you want to take an object and pass it in by value, thus duplicating the object. So it works fine in C++, but you should keep in mind that this scheme fails in Java, so don't use it. What if you're making a library that's so general purpose and commonly used that you cannot make the assumption that it will always be cloned in the proper places? Or more likely, what if you want to allow aliasing for efficiency - to prevent the needless duplication of objects - but you don't want the negative side effects of aliasing? One solution is to create immutable objects which belong to read-only classes. You can define a class such that no methods in the class cause changes to the internal state of the object. In such a class, aliasing has no impact since you can read only the internal state, so if many pieces of code are reading the same object there's no problem. As a simple example of immutable objects, Java's standard library contains wrapper classes for all the primitive types. If you do need an object that holds a primitive type that can be modified, you must create it yourself. Creating read-only classes It's possible to create your own read-only class. Indeed, the method that does appear to modify an object is quadruple( ), but this creates a new Immutable1 object and leaves the original one untouched. The method f( ) takes an Immutable1 object and performs various operations on it, and the output of main( ) demonstrates that there is no change to x. Thus, x's object could be aliased many times without harm because the Immutable1 class is designed to guarantee that objects cannot be changed. The drawback to immutability Creating an immutable class seems at first to provide an elegant solution. However, whenever you do need a modified object of that new type you must suffer the overhead of a new object creation, as well as potentially causing more frequent garbage collections. For some classes this is not a problem, but for others (such as the String class) it is prohibitively expensive. The solution is to create a companion class that can be modified. Then when you're doing a lot of modifications, you can switch to using the modifiable companion class and switch back to the immutable class when you're done. These are the add( ) and multiply( ) methods. The companion class is called Mutable, and it also has add( ) and multiply( ) methods, but these modify the Mutable object rather than making a new one. In addition, Mutable has a method to use its data to produce an Immutable2 object and vice versa. The two static methods modify1( ) and modify2( ) show two different approaches to producing the same result. In modify1( ), everything is done within the Immutable2 class and you can see that four new Immutable2 objects are created in the process. Finally, it's turned back into an Immutable2. Here, two new objects are created (the Mutable and the result Immutable2) instead of four. The object this handle is connected to stays put in a single physical location. The handles are copied as they are passed around. Looking at the definition for upcase( ), you can see that the handle that's passed in has the name s, and it exists for only as long as the body of upcase( ) is being executed. When upcase( ) completes, the local handle s vanishes. Of course, it actually returns a handle to the result. But it turns out that the handle that it returns is for a new object, and the original q is left alone. How does this happen? Implicit constants If you say: String s = asdf; String x = Stringer.upcase(s); do you really want the upcase( ) method to change the argument? In general, you don't, because an argument usually looks to the reader of the code as a piece of information provided to the method, not something to be modified. This is an important guarantee, since it makes code easier to write and understand. In C++, the availability of this guarantee was important enough to put in a special keyword, const, to allow the programmer to ensure that a handle (pointer or reference in C++) could not be used to modify the original object. But then the C++ programmer was required to be diligent and remember to use const everywhere. It can be confusing and easy to forget. Overloading '+' and the StringBuffer Objects of the String class are designed to be immutable, using the technique shown previously. The original String is left untouched. Thus, there's no feature in Java like C++'s const to make the compiler support the immutability of your objects. If you want it, you have to wire it in yourself, like String does. Since String objects are immutable, you can alias to a particular String as many times as you want. Because it's read-only there's no possibility that one handle will change something that will affect the other handles. So a read-only object solves the aliasing problem nicely. It also seems possible to handle all the cases in which you need a modified object by creating a brand new version of the object with the modifications, as String does. However, for some operations this isn't efficient. A case in point is the operator '+' that has been overloaded for String objects. Overloading means that it has been given an extra meaning when used with a particular class. The new String object would then create another new String that added def and so on. This would certainly work, but it requires the creation of a lot of String objects just to put together this new String, and then you have a bunch of the intermediate String objects that need to be garbage-collected. I suspect that the Java designers tried this approach first (which is a lesson in software design - you don't really know anything about a system until you try it out in code and get something working). I also suspect they discovered that it delivered unacceptable performance. The solution is a mutable companion class similar to the one shown previously. For String, this companion class is called StringBuffer, and the compiler automatically creates a StringBuffer to evaluate certain expressions, in particular when the overloaded operators + and += are used with String objects. While this is more efficient, it's worth noting that each time you create a quoted character string such as abc and def, the compiler turns those into String objects. So there can be more objects created than you expect, despite the efficiency afforded through StringBuffer. The String and StringBuffer classes Here is an overview of the methods available for both String and StringBuffer so you can get a feel for the way they interact. These tables don't contain every single method, but rather the ones that are important to this discussion. Methods that are overloaded are summarized in a single row. First, the String class: Method Arguments, Overloading Use Constructor Overloaded: Default, String, StringBuffer, char arrays, byte arrays. Creating String objects. Copy chars or bytes into an external array. An equality check on the contents of the two Strings. Result is negative, zero, or positive depending on the lexicographical ordering of the String and the argument. Uppercase and lowercase are not equal! Overload adds ignore case. Boolean result indicates whether the region matches. Overload adds offset into argument. Boolean result indicates whether the String starts with the argument. Boolean result indicates whether the argument is a suffix. Returns a new String object containing the specified character set. Returns a new String object with the replacements made. Uses the old String if no match is found. Uses the old String if no changes need to be made. Uses the old String if no changes need to be made. Returns a String containing a character representation of the argument. You can see that every String method carefully returns a new String object when it's necessary to change the contents. Also notice that if the contents don't need changing the method will just return a handle to the original String. This saves storage and overhead. Here's the StringBuffer class: Method Arguments, overloading Use Constructor Overloaded: default, length of buffer to create, String to create from. Create a new StringBuffer object. Makes the StringBuffer hold at least the desired number of spaces. Truncates or expands the previous character string. If expanding, pads with nulls. Returns the char at that location in the buffer. Modifies the value at that location. Copy chars into an external array. There's no getBytes( ) as in String. The argument is converted to a string and appended to the end of the current buffer, increasing the buffer if necessary. The second argument is converted to a string and inserted into the current buffer beginning at the offset. The buffer is increased if necessary. The most commonly-used method is append( ), which is used by the compiler when evaluating String expressions that contain the '+' and '+=' operators. The insert( ) method has a similar form, and both methods perform significant manipulations to the buffer instead of creating new objects. Strings are special By now you've seen that the String class is not just another class in Java. There are a lot of special cases in String, not the least of which is that it's a built-in class and fundamental to Java. Summary Because everything is a handle in Java, and because every object is created on the heap and garbage collected only when it is no longer used, the flavor of object manipulation changes, especially when passing and returning objects. For example, in C or C++, if you wanted to initialize some piece of storage in a method, you'd probably request that the user pass the address of that piece of storage into the method. Otherwise you'd have to worry about who was responsible for destroying that storage. Thus, the interface and understanding of such methods is more complicated. But in Java, you never have to worry about responsibility or whether an object will still exist when it is needed, since that is always taken care of for you. Your programs can create an object at the point that it is needed, and no sooner, and never worry about the mechanics of passing around responsibility for that object: you simply pass the handle. Sometimes the simplification that this provides is unnoticed, other times it is staggering. For most applications, the benefits outweigh the drawbacks, and particularly time-critical sections can be written using native methods (see Appendix A). 2. Aliasing: sometimes you can accidentally end up with two handles to the same object, which is a problem only if both handles are assumed to point to a distinct object. This is where you need to pay a little closer attention and, when necessary, clone( ) an object to prevent the other handle from being surprised by an unexpected change. This is certainly a reasonable approach and since clone( ) is supported so rarely within the standard Java library, it is apparently a safe one as well. But as long as you don't call Object.clone( ) you don't need to implement Cloneable or catch the exception, so that would seem acceptable as well. It's interesting to notice that one of the reserved but not implemented keywords in Java is byvalue. After seeing the issues of aliasing and cloning, you can imagine that byvalue might someday be used to implement an automatic local copy in Java. This could eliminate the more complex issues of cloning and make coding in these situations simpler and more robust. Exercises 1. Create a class myString containing a String object that you initialize in the constructor using the constructor's argument. Add a toString( ) method and a method concatenate( ) that appends a String object to your internal string. Implement clone( ) in myString. Create two static methods that each take a myString x handle as an argument and call x.concatenate(test), but in the second method call clone( ) first. Test the two methods and show the different effects. 2. Create a class called Battery containing an int that is a battery number (as a unique identifier). Make it cloneable and give it a toString( ) method. Now create a class called Toy that contains an array of Battery and a toString( ) that prints out all the batteries. Write a clone( ) for Toy that automatically clones all of its Battery objects. Test this by cloning Toy and printing the result. 3. Change CheckCloneable.java so that all of the clone( ) methods catch the CloneNotSupportedException rather than passing it to the caller. 4. Modify Compete.java to add more member objects to classes Thing2 and Thing4 and see if you can determine how the timings vary with complexity - whether it's a simple linear relationship or if it seems more complicated. 5. Starting with Snake.java, create a deep-copy version of the snake. That goal was not achieved. Instead, the Java 1.0 Abstract Window Toolkit (AWT) produces a GUI that looks equally mediocre on all systems. In addition it's restrictive: you can use only four fonts and you cannot access any of the more sophisticated GUI elements that exist in your operating system (OS). The Java 1.0 AWT programming model is also awkward and non-object-oriented. The revision 3 rule of the software industry (a product isn't good until revision 3) seems to hold true with programming languages as well. One of Java's primary design goals is to create applets, which are little programs that run inside a Web browser. Because they must be safe, applets are limited in what they can accomplish. However, they are a powerful tool in supporting client-side programming, a major issue for the Web. Programming within an applet is so restrictive that it's often referred to as being inside the sandbox, since you always have someone - the Java run-time security system - watching over you. Java 1.1 offers digital signing for applets so you can choose to allow trusted applets to have access to your machine. However, you can also step outside the sandbox and write regular applications, in which case you can access the other features of your OS. We've been writing regular applications all along in this book, but they've been console applications without any graphical components. The AWT can also be used to build GUI interfaces for regular applications. In this chapter you'll first learn the use of the original old AWT, which is still supported and used by many of the code examples that you will come across. Although it's a bit painful to learn the old AWT, it's necessary because you must read and maintain legacy code that uses the old AWT. Please be aware that this is not a comprehensive glossary of all the methods for the described classes. This chapter will just get you started with the essentials. When you're looking for more sophistication, make sure you go to your information browser to look for the classes and methods that will solve your problem. Why use the AWT? One of the problems with the old AWT that you'll learn about in this chapter is that it is a poor example of both object-oriented design and GUI development kit design. Many of these problems are reduced or eliminated in Java 1.1 because: 1. The new AWT in Java 1.1 is a much better programming model and a significant step towards a better library. Java Beans is the framework for that library. 2. GUI builders (visual programming environments) will become de rigeur for all development systems. Java Beans and the new AWT allow the GUI builder to write code for you as you place components onto forms using graphical tools. Other component technologies such as ActiveX will be supported in the same fashion. So why learn to use the old AWT? Because it's there. In this case, there has a much more ominous meaning and points to a tenet of object-oriented library design: Once you publicize a component in your library, you can never take it out. If you do, you'll wreck somebody's existing code. In addition, there are many existing code examples out there that you'll read as you learn about Java and they all use the old AWT. The AWT must reach into the GUI components of the native OS, which means that it performs a task that an applet cannot otherwise accomplish. An untrusted applet cannot make any direct calls into an OS because otherwise it could do bad things to the user's machine. The only way an untrusted applet can access important functionality such as draw a window on the screen is through calls in the standard Java library that's been specially ported and safety checked for that machine. The original model that Sun created is that this trusted library will be provided only by the trusted vendor of the Java system in your Web browser, and the vendor will control what goes into that library. But what if you want to extend the system by adding a new component that accesses functionality in the OS? Waiting for Sun to decide that your extension should be incorporated into the standard Java library isn't going to solve your problem. The new model in Java 1.1 is trusted code or signed code whereby a special server verifies that a piece of code that you download is in fact signed by the stated author using a public-key encryption system. This way, you'll know for sure where the code comes from, that it's Bob's code and not just someone pretending to be Bob. This doesn't prevent Bob from making mistakes or doing something malicious, but it does prevent Bob from shirking responsibility - anonymity is what makes computer viruses possible. A digitally signed applet - a trusted applet - in Java 1.1 can reach into your machine and manipulate it directly, just like any other application you get from a trusted vendor and install onto your computer. But the point of all this is that the old AWT is there. There will always be old AWT code floating around and new Java programmers learning from old books will encounter that code. Also, the old AWT is worth studying as an example of poor library design. The coverage of the old AWT given here will be relatively painless since it won't go into depth and enumerate every single method and class, but instead give you an overview of the old AWT design. The basic applet Libraries are often grouped according to their functionality. Some libraries, for example, are used as is, off the shelf. The standard Java library String and Vector classes are examples of these. Other libraries are designed specifically as building blocks to build other classes. A certain class of library is the application framework, whose goal is to help you build applications by providing a class or set of classes that produces the basic behavior that you need in every application of a particular type. Then, to customize the behavior to your own needs you inherit from the application class and override the methods of interest. The application framework's default control mechanism will call your overridden methods at the appropriate time. An application framework is a good example of separating the things that change from the things that stay the same, since it attempts to localize all the unique parts of a program in the overridden methods. Applets are built using an application framework. You inherit from class Applet and override the appropriate methods. Most of the time you'll be concerned with only a few important methods that have to do with how the applet is built and used on a Web page. Also called after init( ). Called as part of an update( ) to perform special painting on the canvas of an applet. Also called right before destroy( ). The applet calls its update( ) method (defined in the base class Component), which goes about restoring everything, and as a part of that restoration calls paint( ). You don't have to override paint( ), but it turns out to be an easy way to make a simple applet, so we'll start out with paint( ). When update( ) calls paint( ) it hands it a handle to a Graphics object that represents the surface on which you can paint. This is important because you're limited to the surface of that particular component and thus cannot paint outside that area, which is a good thing or else you'd be painting outside the lines. In the case of an applet, the surface is the area inside the applet. The Graphics object also has a set of operations you can perform on it. These operations revolve around painting on the canvas, so most of them have to do with drawing images, shapes, arcs, etc. For this, you must specify the String you want to draw and its starting location on the applet's drawing surface. This location is given in pixels, so it will look different on different machines, but at least it's portable. That's all wired in to the application framework; you put any startup code in init( ). To run this program you must place it inside a Web page and view that page inside your Java-enabled Web browser. To place an applet inside a Web page you put a special tag inside the HTML source for that Web page1 to tell the page how to load and run the applet. The width and height specify the initial size of the applet (in pixels, as before). For simple applets all you need to do is place an applet tag in the above form inside your Web page and that will load and run the applet. Testing applets You can perform a simple test without any network connection by starting up your Web browser and opening the HTML file containing the applet tag. Of course, it looks at the CLASSPATH to find out where to hunt, and if your .class file isn't in the CLASSPATH then it will give an error message on the status line of the browser to the effect that it couldn't find that .class file. When you want to try this out on your Web site things are a little more complicated. First of all, you must have a Web site, which for most people means a third-party Internet Service Provider (ISP) at a remote location. Then you must have a way to move the HTML files and the.class files from your site to the correct directory (your WWW directory) on the ISP machine. This is typically done with a File Transfer Protocol (FTP) program, of which there are many different types freely available. So it would seem that all you need to do is move the files to the ISP machine with FTP, then connect to the site and HTML file using your browser; if the applet comes up and works, then everything checks out, right? Here's where you can get fooled. If the browser cannot locate the.class file on the server, it will hunt through the CLASSPATH on your local machine. Thus, the applet might not be loading properly from the server, but to you it looks fine because the browser finds it on your machine. When someone else logs in, however, his or her browser can't find it. So when you're testing, make sure you erase the relevant .class files on your machine to be safe. One of the most insidious places where this happened to me is when I innocently placed an applet inside a package. After uploading the HTML file and applet, it turned out that the server path to the applet was confused because of the package name. However, my browser found it in the local CLASSPATH. So I was the only one who could properly load the applet. It took some time to discover that the package statement was the culprit. In general, you'll want to leave the package statement out of an applet. Of course, all the numbers are hard-coded and are based on pixels, so on some machines the box will fit nicely around the string and on others it will probably be off, because fonts will be different on different machines. There are other interesting things you can find in the documentation for the Graphic class. Any sort of graphics activity is usually entertaining, so further experiments of this sort are left to the reader. Demonstrating the framework methods It's interesting to see some of the framework methods in action. For example, with init( ) you might need to call super.init( ). However, the Applet documentation specifically states that the init( ), start( ), and stop( ) methods in Applet do nothing, so it's not necessary to call them here. When you experiment with this applet you'll discover that if you minimize the Web browser or cover it up with another window you might not get calls to stop( ) and start( ). Making a button Making a button is quite simple: you just call the Button constructor with the label you want on the button. The Button is a component, like its own little window, that will automatically get repainted as part of an update. This means that you don't explicitly paint a button or any other kind of control; you simply place them on the form and let them automatically take care of painting themselves. You must also call the Applet add( ) method to cause the button to be placed on the applet's form. This seems a lot simpler than it is, because the call to add( ) actually decides, implicitly, where to place the control on the form. Controlling the layout of a form is examined shortly. Capturing an event You'll notice that if you compile and run the applet above, nothing happens when you press the buttons. This is where you must step in and write some code to determine what will happen. The basis of event-driven programming, which comprises a lot of what a GUI is about, is tying events to code that responds to those events. After working your way this far through the book and grasping some of the fundamentals of object-oriented programming, you might think that of course there will be some sort of object-oriented approach to handling events. For example, you might have to inherit each button and override some button pressed method (this, it turns out, is too tedious and restrictive). You might also think there's some master event class that contains a method for each event you want to respond to. Before objects, the typical approach to handling events was the giant switch statement. Each event would have a unique integer value and inside the master event handling method you'd write a switch on that value. The AWT in Java 1.0 doesn't use any object-oriented approach. Neither does it use a giant switch statement that relies on the assignment of numbers to events. Instead, you must create a cascaded set of if statements. What you're trying to do with the if statements is detect the object that was the target of the event. That is, if you click on a button, then that particular button is the target. Normally, that's all you care about - if a button is the target of an event, then it was most certainly a mouse click and you can continue based on that assumption. However, events can contain other information as well. For example, if you want to find out the pixel location where a mouse click occurred so you can draw a line to that location, the Event object will contain the location. For example, it could be a mouse click, a normal keyboard press or release, a special key press or release, the fact that the component got or lost the focus, mouse movements, or drags, etc. The second argument is usually the target of the event, which you'll often ignore. The second argument is also encapsulated in the Event object so it is redundant as an argument. For example, with a button the action( ) method is called when the button is pressed and at no other time. Usually this is just fine, since that's what you ordinarily look for with a button. However, it's possible to deal with many other types of events via the handleEvent( ) method as we will see later in this chapter. When you've written handlers for all the objects you're interested in you must call super.action(evt, arg) in the else statement at the end, as shown above. Remember from Chapter 7 (polymorphism) that your overridden method is called instead of the base class version. However, the base-class version contains code to handle all of the cases that you're not interested in, and it won't get called unless you call it explicitly. The return value indicates whether you've handled it or not, so if you do match an event you should return true, otherwise return whatever the base-class event( ) returns. For this example, the simplest action is to print what button is pressed. Some systems allow you to pop up a little window with a message in it, but applets discourage this. Strange as it might seem, you can also match an event to the text that's on a button through the second argument in event( ). You should avoid this approach if possible. Text fields A TextField is a one line area that allows the user to enter and edit text. Pressing button 1 either gets the text you've selected with the mouse or it gets all the text in the field and places the result in String s. It also allows the field to be edited. Pressing button 2 puts a message and s into the text field and prevents the field from being edited (although you can still select the text). The editability of the text is controlled by passing setEditable( ) a true or false. Text areas A TextArea is like a TextField except that it can have multiple lines and has significantly more functionality. In addition to what you can do with a TextField, you can append text and insert or replace text at a given location. It seems like this functionality could be useful for TextField as well, so it's a little confusing to try to detect how the distinction is made. You might think that if you want TextArea functionality everywhere you can simply use a one line TextArea in places where you would otherwise use a TextField. In Java 1.0, you also got scroll bars with a TextArea even when they weren't appropriate; that is, you got both vertical and horizontal scroll bars for a one line TextArea. In Java 1.1 this was remedied with an extra constructor that allows you to select which scroll bars (if any) are present. The following example shows only the Java 1.0 behavior, in which the scrollbars are always on. Later in the chapter you'll see an example that demonstrates Java 1.1 TextAreas. The different buttons show getting, appending, replacing, and inserting text. Labels A Label does exactly what it sounds like it should: places a label on the form. This is particularly important for text fields and text areas that don't have labels of their own, and can also be useful if you simply want to place textual information on a form. You can, as shown in the first example in this chapter, use drawString( ) inside paint( ) to place text in an exact location. When you use a Label it allows you to (approximately) associate the text with some other component via the layout manager (which will be discussed later in this chapter). With the constructor you can create a blank label, a label with initial text in it (which is what you'll typically do), and a label with an alignment of CENTER, LEFT, or RIGHT (static final ints defined in class Label). You can also change the label and its alignment with setText( ) and setAlignment( ), and if you've forgotten what you've set these to you can read the values with getText( ) and getAlignment( ). In the second part of the example, a bunch of empty spaces are reserved and when you press the Test 1 button setText( ) is used to insert text into the field. Because a number of blank spaces do not equal the same number of characters (in a proportionally-spaced font) you'll see that the text gets truncated when inserted into the label. The rest of the times you press the button it changes the alignment so you can see the effect. You might think that you could create an empty label and then later put text in it with setText( ). However, you cannot put text into an empty label - presumably because it has zero width - so creating a label with no text seems to be a useless thing to do. In the example above, the blank label is filled with empty spaces so it has enough width to hold text that's placed inside later. Similarly, setAlignment( ) has no effect on a label that you'd typically create with text in the constructor. The label width is the width of the text, so changing the alignment doesn't do anything. However, if you start with a long label and then change it to a shorter one you can see the effect of the alignment. These behaviors occur because of the default layout manager that's used for applets, which causes things to be squished together to their smallest size. Layout managers will be covered later in this chapter, when you'll see that other layouts don't have the same effect. Check boxes A check box provides a way to make a single on-off choice; it consists of a tiny box and a label. The box typically holds a little 'x' (or some other indication that it is set) or is empty depending on whether that item was selected. You'll normally create a Checkbox using a constructor that takes the label as an argument. You can get and set the state, and also get and set the label if you want to read or change it after the Checkbox has been created. Radio buttons The concept of a radio button in GUI programming comes from pre-electronic car radios with mechanical buttons: when you push one in, any other button that was pressed pops out. Thus it allows you to force a single choice among many. The AWT does not have a separate class to represent the radio button; instead it reuses the Checkbox. However, to put the Checkbox in a radio button group (and to change its shape so it's visually different from an ordinary Checkbox) you must use a special constructor that takes a CheckboxGroup object as an argument. One of the Checkbox objects must have its state set to true before you try to display the group of radio buttons; otherwise you'll get an exception at run time. If you try to set more than one radio button to true then only the final one set will be true. Here's a simple example of the use of radio buttons. This field is set to non-editable because it's used only to display data, not to collect it. This is shown as an alternative to using a Label. Notice the text in the field is initialized to Radio button 2 since that's the initial selected radio button. You can have any number of CheckboxGroups on a form. Drop-down lists Like a group of radio buttons, a drop-down list is a way to force the user to select only one element from a group of possibilities. However, it's a much more compact way to accomplish this, and it's easier to change the elements of the list without surprising the user. Java's Choice box is not like the combo box in Windows, which lets you select from a list or type in your own selection. With a Choice box you choose one and only one element from the list. In the following example, the Choice box starts with a certain number of entries and then new entries are added to the box when a button is pressed. When you run this applet, pay attention to the determination of the size of the Choice box: in Windows, the size is fixed from the first time you drop down the list. This means that if you drop down the list, then add more elements to the list, the elements will be there but the drop-down list won't get any longer4 (you can scroll through the elements). However, if you add all the elements before the first time the list is dropped down, then it will be sized correctly. Of course, the user will expect to see the whole list when it's dropped down, so this behavior puts some significant limitations on adding elements to Choice boxes. List boxes List boxes are significantly different from Choice boxes, and not just in appearance. While a Choice box drops down when you activate it, a List occupies some fixed number of lines on a screen all the time and doesn't change. In addition, a List allows multiple selection: if you click on more than one item the original item stays highlighted and you can select as many as you want. If you want to see the items in a list, you simply call getSelectedItems( ), which produces an array of String of the items that have been selected. To remove an item from a group you have to click it again. A problem with a List is that the default action is double clicking, not single clicking. A single click adds or removes elements from the selected group and a double click calls action( ). However, the only way for action( ) to be called is through a double-click. If you need to monitor other activities that the user is doing on your List (in particular, single clicks) you must take an alternative approach. Any time an event happens, it happens over or to a particular object. The handleEvent( ) method for that object is automatically called and an Event object is created and passed to handleEvent( ). We'll look at those later in this chapter. What if these other methods - action( ) in particular - don't satisfy your needs? In the case of List, for example, what if you want to catch single mouse clicks but action( ) responds to only double clicks? The solution is to override handleEvent( ) for your applet, which after all is derived from Applet and can therefore override any non-final methods. When you override handleEvent( ) for the applet you're getting all the applet events before they are routed, so you cannot just assume This has to do with my button so I can assume it's been pressed, since that's true only for action( ). Inside handleEvent( ) it's possible that the button has the focus and someone is typing to it. Whether it makes sense or not, those are events that you can detect and act upon in handleEvent( ). Inside, a check is made to see whether a list selection or deselection has occurred. Now remember, handleEvent( ) is being overridden for the applet, so this occurrence could be anywhere on the form and it could be happening to another list. Thus, you must also check to see what the target is. This is bad practice since it's going to be a problem as soon as another list is added.) If the list matches the one we're interested in, the same code as before will do the trick. Note that the form for handleEvent( ) is similar to action( ): if you deal with a particular event you return true, but if you're not interested in any of the other events via handleEvent( ) you must return super.handleEvent(evt). This is vital because if you don't do this, none of the other event-handling code will get called. For example, try commenting out the return super.handleEvent(evt) in the code above. You'll discover that action( ) never gets called, certainly not what you want. For both action( ) and handleEvent( ) it's important to follow the format above and always return the base-class version of the method when you do not handle the event yourself (in which case you should return true). This is nice because it allows the user to choose a single or multiple selection rather than fixing it during programming. You might think you'll be clever and implement this yourself by checking to see if the shift key is held down when a mouse click was made by testing for evt.shiftDown( ). Alas, the design of the AWT stymies you - you'd have to be able to know which item was clicked on if the shift key wasn't pressed so you could deselect all the rest and select only that one. First, it's all code; there are no resources that control placement of components. Second, the way components are placed on a form is controlled by a layout manager that decides how the components lie based on the order that you add( ) them. The size, shape, and placement of components will be remarkably different from one layout manager to another. Both the Applet and Frame classes are derived from Container, whose job it is to contain and display Components. In this section we'll explore the various layout managers by placing buttons in them (since that's the simplest thing to do). There won't be any capturing of button events since this is just intended to show how the buttons are laid out. FlowLayout So far, all the applets that have been created seem to have laid out their components using some mysterious internal logic. That's because the applet uses a default layout scheme: the FlowLayout. This simply flows the components onto the form, from left to right until the top space is full, then moves down a row and continues flowing the components. Here's an example that explicitly (redundantly) sets the layout manager in an applet to FlowLayout and then places buttons on the form. You'll notice that with FlowLayout the components take on their natural size. A Button, for example, will be the size of its string. For example, a label will be the size of its string, so right-justifying it yields an unchanged display. BorderLayout This layout manager has the concept of four border regions and a center area. Center, however, spreads out along both dimensions to occupy the middle. The BorderLayout is the default layout manager for applications and dialogs. GridLayout A GridLayout allows you to build a table of components, and as you add them they are placed left-to-right and top-to-bottom in the grid. In the constructor you specify the number of rows and columns that you need and these are laid out in equal proportions. The last slot is left empty; no balancing goes on with a GridLayout. Not so in the AWT: The CardLayout is simply a blank space and you're responsible for bringing forward new cards. This contains a single button, placed at the center of a BorderLayout, which means that it will expand to fill the entire panel. The label on the button will let you know which panel you're on in the CardLayout. In the applet, both the Panel cards where the cards will live and the layout manager cl for the CardLayout must be members of the class because you need to have access to those handles when you want to manipulate the cards. The applet is changed to use a BorderLayout instead of its default FlowLayout, a Panel is created to hold three buttons (using a FlowLayout), and this panel is placed at the North end of the applet. The cards panel is added to the Center of the applet, effectively occupying the rest of the real estate. When you add the ButtonPanels (or whatever other components you want) to the panel of cards, the add( ) method's first argument is not North, South, etc. Instead, it's a string that describes the card. Although this string doesn't show up anywhere on the card, you can use it if you want to flip that card using the string. This approach is not used in action( ); instead the first( ), next( ), and last( ) methods are used. Check your documentation for the other approach. In Java, the use of some sort of tabbed panel mechanism is quite important because (as you'll see later) in applet programming the use of pop-up dialogs is heavily discouraged. For Java 1.0 applets, the CardLayout is the only viable way for the applet to have a number of different forms that pop up on command. GridBagLayout Some time ago, it was believed that all the stars, planets, the sun, and the moon revolved around the earth. It seemed intuitive from observation. But then astronomers became more sophisticated and started tracking the motion of individual objects, some of which seemed at times to go backward in their paths. Since it was known that everything revolved around the earth, those astronomers spent large amounts of time coming up with equations and theories to explain the motion of the stellar objects. When trying to work with GridBagLayout, you can consider yourself the analog of one of those early astronomers. The basic precept (decreed, interestingly enough, by the designers at Sun) is that everything should be done in code. The Copernican revolution (again dripping with irony, the discovery that the planets in the solar system revolve around the sun) is the use of resources to determine the layout and make the programmer's job easy. Until these are added to Java, you're stuck (to continue the metaphor) in the Spanish Inquisition of GridBagLayout and GridBagConstraints. My recommendation is to avoid GridBagLayout. Instead, use the other layout managers and especially the technique of combining several panels using different layout managers within a single program. Your applets won't look that different; at least not enough to justify the trouble that GridBagLayout entails. For my part, it's just too painful to come up with an example for this (and I wouldn't want to encourage this kind of library design). Instead, I'll refer you to Core Java by Cornell and Horstmann (2nd ed., Prentice-Hall, 1997) to get started. Alternatives to action As noted previously, action( ) isn't the only method that's automatically called by handleEvent( ) once it sorts everything out for you. There are three other sets of methods that are called, and if you want to capture certain types of events (keyboard, mouse, and focus events) all you have to do is override the provided method. These methods are defined in the base class Component, so they're available in virtually all the controls that you might place on a form. However, you should be aware that this approach is deprecated in Java 1.1, so although you might see legacy code using this technique you should use the Java 1.1 approaches (described later in this chapter) instead. The second argument is the key that was pressed and is redundantly copied from evt.key. Normally, what is redundantly copied from evt.arg. All drag events are reported to the component in which the mouseDown occurred until there is a mouseUp. It's interesting to note that when Component's handleEvent( ) calls any of these methods (the typical case), the extra arguments are always redundant as they are contained within the Event object. In fact, if you look at the source code for Component.handleEvent( ) you can see that it explicitly plucks the additional arguments out of the Event object. This example also shows you how to make your own button object because that's what is used as the target of all the events of interest. You might first (naturally) assume that to make a new button, you'd inherit from Button. But this doesn't work. Instead, you inherit from Canvas (a much more generic component) and paint your button on that canvas by overriding the paint( ) method. As you'll see, it's really too bad that overriding Button doesn't work, since there's a bit of code involved to paint the button. You'll see that the button doesn't get painted and the events don't get handled.) The myButton class is specific: it works only with an AutoEvent parent window (not a base class, but the window in which this button is created and lives). With this knowledge, myButton can reach into the parent window and manipulate its text fields, which is what's necessary to be able to write the status information into the fields of the parent. Of course this is a much more limited solution, since myButton can be used only in conjunction with AutoEvent. This kind of code is sometimes called highly coupled. However, to make myButton more generic requires a lot more effort that isn't warranted for this example (and possibly for many of the applets that you will write). Notice the use of size( ) to determine the width and height of the component (in pixels, of course). You can't understand exactly how the keyDown( ), keyUp( ), etc. This contains a Hashtable to hold the strings representing the type of event and the TextField where information about that event is held. Of course, these could have been created statically rather than putting them in a Hashtable, but I think you'll agree that it's a lot easier to use and change. In particular, if you need to add or remove a new type of event in AutoEvent, you simply add or remove a string in the event array - everything else happens automatically. The place where you look up the strings is in the keyDown( ), keyUp( ), etc. Each of these methods uses the parent handle to reach back to the parent window. Since that parent is an AutoEvent it contains the Hashtable h, and the get( ) method, when provided with the appropriate String, will produce a handle to an Object that we happen to know is a TextField - so it is cast to that. Then the Event object is converted to its String representation, which is displayed in the TextField. It turns out this example is rather fun to play with since you can really see what's going on with the events in your program. Applet restrictions For safety's sake, applets are quite restricted and there are many things you can't do. You can generally answer the question of what an applet is able to do by looking at what it is supposed to do: extend the functionality of a Web page in a browser. Since, as a net surfer, you never really know if a Web page is from a friendly place or not, you want any code that it runs to be safe. So the biggest restrictions you'll notice are probably: 1) An applet can't touch the local disk. This means writing or reading, since you wouldn't want an applet to read and transmit important information about you across the Web. Writing is prevented, of course, since that would be an open invitation to a virus. These restrictions can be relaxed when digital signing is fully implemented. 2) An applet can't have menus. You might have noticed that an applet looks like it blends right in as part of a Web page; you often don't see the boundaries of the applet. There's no frame or title bar to hang the menu from, other than the one belonging to the Web browser. Perhaps the design could be changed to allow you to merge your applet menu with the browser menu - that would be complicated and would also get a bit too close to the edge of safety by allowing the applet to affect its environment. 3) Dialog boxes are untrusted. In Java, dialog boxes present a bit of a quandary. First of all, they're not exactly disallowed in applets but they're heavily discouraged. If you pop up a dialog box from within an applet you'll get an untrusted applet message attached to that dialog. This is because, in theory, it would be possible to fool the user into thinking that they're dealing with a regular native application and to get them to type in their credit card number, which then goes across the Web. After seeing the kinds of GUIs that the AWT produces you might have a hard time believing anybody could be fooled that way. But an applet is always attached to a Web page and visible within your Web browser, while a dialog box is detached so in theory it could be possible. As a result it will be rare to see an applet that uses a dialog box. Many applet restrictions are relaxed for trusted applets (those signed by a trusted source) in newer browsers. There are other issues when thinking about applet development: * Applets take longer to download since you must download the whole thing every time, including a separate server hit for each different class. Your browser can cache the applet, but there are no guarantees. Digital signing (the ability to verify the creator of a class) is available for each individual entry in the JAR file. For example, you can't have a modal dialog box within an applet, since the user can always switch the page. In addition, different browsers do different things to your applet when you leave a Web page so the results are essentially undefined. An applet has true platform independence (including the ability to easily play audio files, etc.) so you don't need to make any changes in your code for different platforms nor does anyone have to perform any tweaking upon installation. In fact, installation is automatic every time the user loads the Web page along with the applets, so updates happen silently and automatically. It's an interesting twist, since we're used to having the documentation part of the program rather than vice versa. Windowed applications It's possible to see that for safety's sake you can have only limited behavior within an applet. In a real sense, the applet is a temporary extension to the Web browser so its functionality must be limited along with its knowledge and control. In previous chapters in this book we've made command-line applications, but in some operating environments (the Macintosh, for example) there isn't a command line. So for any number of reasons you'd like to build a windowed, non-applet program using Java. This is certainly a reasonable desire. A Java windowed application can have menus and dialog boxes (impossible or difficult with an applet), and yet if you're using an older version of Java you sacrifice the native operating environment's look and feel. If you want to build windowed applications, it makes sense to do so only if you can use the latest version of Java and associated tools so you can deliver applications that won't confound your users. If for some reason you're forced to use an older version of Java, think hard before committing to building a significant windowed application. Menus It's impossible to put a menu directly on an applet (in Java 1.0 and Java 1.1; the Swing library does allow it), so they're for applications. Go ahead, try it if you don't believe me and you're sure that it would make sense to have menus on applets. There's no setMenuBar( ) method in Applet and that's the way a menu is attached. Unlike a system that uses resources, with Java and the AWT you must hand assemble all the menus in source code. Instead, I placed the menu items into arrays and then simply stepped through each array calling add( ) in a for loop. This makes adding or subtracting a menu item less tedious. As an alternative approach (which I find less desirable since it requires more typing), the CheckboxMenuItems are created in an array of handles called safety; this is true for the arrays file and other as well. This program creates not one but two MenuBars to demonstrate that menu bars can be actively swapped while the program is running. You can see how a MenuBar is made up of Menus, and each Menu is made up of MenuItems, CheckboxMenuItems, or even other Menus (which produce submenus). When a MenuBar is assembled it can be installed into the current program with the setMenuBar( ) method. Note that when the button is pressed, it checks to see which menu is currently installed using getMenuBar( ), then puts the other menu bar in its place. When testing for Open, notice that spelling and capitalization are critical, but Java signals no error if there is no match with Open. This kind of string comparison is a clear source of programming errors. The checking and un-checking of the menu items is taken care of automatically, but dealing with CheckboxMenuItems can be a bit surprising since for some reason they don't allow string matching. As shown, the getState( ) method can be used to reveal the state. You can also change the state of a CheckboxMenuItem with setState( ). You might think that one menu could reasonably reside on more than one menu bar. This does seem to make sense because all you're passing to the MenuBar add( ) method is a handle. However, if you try this, the behavior will be strange and not what you expect. Instead of init( ) to set things up, you make a constructor for your class. Finally, you create a main( ) and in that you build an object of your new type, resize it, and then call show( ). It's different from an applet in only a few small places, but it's now a standalone windowed application and you've got menus. Dialog boxes A dialog box is a window that pops up out of another window. Its purpose is to deal with some specific issue without cluttering the original window with those details. Dialog boxes are heavily used in windowed programming environments, but as mentioned previously, rarely used in applets. To create a dialog box, you inherit from Dialog, which is just another kind of Window, like a Frame. Unlike a Frame, a Dialog cannot have a menu bar or change the cursor, but other than that they're quite similar. A dialog has a layout manager (which defaults to BorderLayout) and you override action( ) etc., or handleEvent( ) to deal with events. One significant difference you'll want to note in handleEvent( ): when the WINDOW_DESTROY event occurs, you don't want to shut down the application! Instead, you release the resources used by the dialog's window by calling dispose( ). In the following example, the dialog box is made up of a grid (using GridLayout) of a special kind of button that is defined here as class ToeButton. This button draws a frame around itself and, depending on its state, a blank, an x, or an o in the middle. It starts out blank, and then depending on whose turn it is, changes to an x or an o. However, it will also flip back and forth between x and o when you click on the button. Of course, you can take another approach, which is to make ToeDialog.turn a static member of ToeButton. This eliminates the coupling, but prevents you from having more than one ToeDialog at a time. A mouse click is captured by the overridden mouseDown( ) method, which first checks to see if the button has anything written on it. If not, the parent window is queried to find out whose turn it is and that is used to establish the state of the button. Note that the button then reaches back into the parent and changes the turn. If the button is already displaying an x or an o then that is flopped. You can see in these calculations the convenient use of the ternary if-else described in Chapter 3. After a button state change, the button is repainted. The constructor for ToeDialog is quite simple: it adds into a GridLayout as many buttons as you request, then resizes it for 50 pixels on a side for each button. ToeTest sets up the whole application by creating the TextFields (for inputting the rows and columns of the button grid) and the go button. You'll see in action( ) that this program uses the less-desirable string match technique for detecting the button press (make sure you get spelling and capitalization right!). When the button is pressed, the data in the TextFields must be fetched, and, since they are in String form, turned into ints using the static Integer.parseInt( ) method. Once the Dialog is created, the show( ) method must be called to display and activate it. You'll notice that the ToeDialog object is assigned to a Dialog handle d. This is an example of upcasting, although it really doesn't make much difference here since all that's happening is the show( ) method is called. However, if you wanted to call some method that existed only in ToeDialog you would want to assign to a ToeDialog handle and not lose the information in an upcast. File dialogs Some operating systems have a number of special built-in dialog boxes to handle the selection of things such as fonts, colors, printers, and the like. Virtually all graphical operating systems support the opening and saving of files, however, and so Java's FileDialog encapsulates these for easy use. This, of course, makes no sense at all to use from an applet since an applet can neither read nor write files on the local disk. The method setFile( ) provides an initial file name - presumably the native OS supports wildcards, so in this example all the.java files will initially be displayed. The setDirectory( ) method chooses the directory where the file selection will begin. The FileDialog object still exists, so you can read data from it. If you call getFile( ) and it returns null it means the user canceled out of the dialog. Both the file name and the results of getDirectory( ) are displayed in the TextFields. The button for saving works the same way, except that it uses a different constructor for the FileDialog. This constructor takes three arguments and the third argument must be either FileDialog.SAVE or FileDialog.OPEN. The new AWT In Java 1.1 a dramatic change has been accomplished in the creation of the new AWT. Most of this change revolves around the new event model used in Java 1.1: as bad, awkward, and non-object-oriented as the old event model was, the new event model is possibly the most elegant I have seen. It's difficult to understand how such a bad design (the old AWT) and such a good one (the new event model) could come out of the same group. This new way of thinking about events seems to drop so easily into your mind that the issue no longer becomes an impediment; instead, it's a tool that helps you design the system. It's also essential for Java Beans, described later in the chapter. Instead of the non-object-oriented cascaded if statements in the old AWT, the new approach designates objects as sources and listeners of events. As you will see, the use of inner classes is integral to the object-oriented nature of the new event model. In addition, events are now represented in a class hierarchy instead of a single class, and you can create your own event types. You'll also find, if you've programmed with the old AWT, that Java 1.1 has made a number of what might seem like gratuitous name changes. For example, setSize( ) replaces resize( ). This will make sense when you learn about Java Beans, because Beans use a particular naming convention. The names had to be modified to make the standard AWT components into Beans. Java 1.1 continues to support the old AWT to ensure backward compatibility with existing programs. Without fully admitting disaster, the online documents for Java 1.1 list all the problems involved with programming the old AWT and describe how those problems are addressed in the new AWT. Clipboard operations are supported in 1.1, although drag-and-drop will be supported in a future release. You can access the desktop color scheme so your Java program can fit in with the rest of the desktop. Pop-up menus are available, and there are some improvements for graphics and images. Mouseless operation is supported. There is a simple API for printing and simplified support for scrolling. The new event model In the new event model a component can initiate (fire) an event. Each type of event is represented by a distinct class. When an event is fired, it is received by one or more listeners, which act on that event. Thus, the source of an event and the place where the event is handled can be separate. Each event listener is an object of a class that implements a particular type of listener interface. So as a programmer, all you do is create a listener object and register it with the component that's firing the event. This registration is performed by calling a addXXXListener( ) method in the event-firing component, in which XXX represents the type of event listened for. You can easily know what types of events can be handled by noticing the names of the addListener methods, and if you try to listen for the wrong events you'll find out your mistake at compile time. Java Beans also uses the names of the addListener methods to determine what a Bean can do. All of your event logic, then, will go inside a listener class. When you create a listener class, the sole restriction is that it must implement the appropriate interface. A simple example will make this clear. Consider the Button2.java example from earlier in this chapter. In init( ), the only change is the addition of the two lines: b1.addActionListener(new B1()); b2.addActionListener(new B2()); addActionListener( ) tells a button which object to activate when the button is pressed. The classes B1 and B2 are inner classes that implement the interface ActionListener. This interface contains a single method actionPerformed( ) (meaning This is the action that will be performed when the event is fired). Note that actionPerformed( ) does not take a generic event, but rather a specific type of event, ActionEvent. So you don't need to bother testing and downcasting the argument if you want to extract specific ActionEvent information. One of the nicest things about actionPerformed( ) is how simple it is. It's just a method that gets called. Compare it to the old action( ) method, in which you must figure out what happened and act appropriately, and also worry about calling the base class version of action( ) and return a value to indicate whether it's been handled. With the new event model you know that all the event-detection logic is taken care of so you don't have to figure that out; you just say what happens and you're done. If you're don't already prefer this approach over the old one, you will soon. Event and listener types All the AWT components have been changed to include addXXXListener( ) and removeXXXListener( ) methods so that the appropriate types of listeners can be added and removed from each component. You'll notice that the XXX in each case also represents the argument for the method, for example, addFooListener(FooListener fl). The following table includes the associated events, listeners, methods, and the components that support those particular events by providing the addXXXListener( ) and removeXXXListener( ) methods. You simply: 1. Take the name of the event class and remove the word Event. Add the word Listener to what remains. This is the listener interface you need to implement in your inner class. 2. Implement the interface above and write out the methods for the events you want to capture. For example, you might be looking for mouse movements, so you write code for the mouseMoved( ) method of the MouseMotionListener interface. Register it with your component with the method produced by prefixing add to your listener name. For example, addMouseMotionListener( ). These are trivial to implement since you'll implement them only when you want to write that particular method. However, the listener interfaces that have multiple methods could be less pleasant to use. For example, something you must always do when creating an application is provide a WindowListener to the Frame so that when you get the windowClosing( ) event you can call System.exit(0) to exit the application. But since WindowListener is an interface, you must implement all of the other methods even if they don't do anything. This can be annoying. To solve the problem, each of the listener interfaces that have more than one method are provided with adapters, the names of which you can see in the table above. Each adapter provides default methods for each of the interface methods. There is a downside to adapters, however, in the form of a pitfall. Can you see the problem? It's in the name of the method: WindowClosing( ) instead of windowClosing( ). A simple slip in capitalization results in the addition of a completely new method. However, this is not the method that's called when the window is closing, so you don't get the desired results. Making windows and applets with the Java 1.1 AWT Often you'll want to be able to create a class that can be invoked as either a window or an applet. To accomplish this, you simply add a main( ) to your applet that builds an instance of the applet inside a Frame. In fact, you can usually copy and paste the WL class and main( ) into your own applets with little modification. The WL class is static so it can be easily created in main( ). Making it static eliminates this need.) You can see that in main( ), the applet is explicitly initialized and started since in this case the browser isn't available to do it for you. Of course, this doesn't provide the full behavior of the browser, which also calls stop( ) and destroy( ), but for most situations it's acceptable. Notice the last line: aFrame.setVisible(true); This is one of the changes in the Java 1.1 AWT. The show( ) method is deprecated and setVisible(true) replaces it. These sorts of seemingly capricious changes will make more sense when you learn about Java Beans later in the chapter. This example is also modified to use a TextField rather than printing to the console or to the browser status line. One restriction in making a program that's both an applet and an application is that you must choose input and output forms that work for both situations. There's another small new feature of the Java 1.1 AWT shown here. You no longer need to use the error-prone approach of specifying BorderLayout positions using a String. This is a definite improvement, and will be used throughout the rest of the book. Making the window listener an anonymous class Any of the listener classes could be implemented as anonymous classes, but there's always a chance that you might want to use their functionality elsewhere. However, the window listener is used here only to close the application's window so you can safely make it an anonymous class. You must decide for yourself whether it makes the code easier to understand or more difficult. However, for the remainder of the book an anonymous inner class will usually be used for the window listener. Packaging the applet into a JAR file An important JAR use is to optimize applet loading. In Java 1.0, people tended to try to cram all their code into a single Applet class so the client would need only a single server hit to download the applet code. Not only did this result in messy, hard to read (and maintain) programs, but the.class file was still uncompressed so downloading wasn't as fast as it could have been. JAR files change all of that by compressing all of your.class files into a single file that is downloaded by the browser. Now you don't need to create an ugly design to minimize the number of classes you create, and the user will get a much faster download time. Consider the example above. It looks like Button2NewB is a single class, but in fact it contains three inner classes, so that's four in all. In addition, each program is now both an applet and an application so you can run it with or without a browser. You'll see that the action listener for a TextField is fired only when you press the enter key. The TextField t1 has several listeners attached to it. The T1 listener copies all text from t1 into t2 and the T1K listener forces all characters to upper case. You'll notice that the two work together, and if you add the T1K listener after you add the T1 listener, it doesn't matter: all characters will still be forced to upper case in both text fields. It would seem that keyboard events are always fired before TextComponent events, and if you want the characters in t2 to retain the original case that was typed in, you must do some extra work. T1K has some other activities of interest. You must detect a backspace (since you're controlling everything now) and perform the deletion. The caret must be explicitly set to the end of the field; otherwise it won't behave as you expect. Finally, to prevent the original character from being handled by the default mechanism, the event must be consumed using the consume( ) method that exists for event objects. This tells the system to stop firing the rest of the event handlers for this particular event. This example also quietly demonstrates one of the benefits of the design of inner classes. This is because an object of an inner class automatically captures a handle to the outer object that created it, so you can treat members and methods of the enclosing class object as if they're yours. As you can see, this is quite convenient.6 Text areas The most significant change to text areas in Java 1.1 concerns scroll bars. With the TextArea constructor, you can now control whether a TextArea will have scroll bars: vertical, horizontal, both, or neither. Also, even if a TextArea doesn't have a scrollbar, you can move the cursor such that scrolling will be forced. In either case, the interesting event is ItemEvent, for which you create an ItemListener. When dealing with a group of check boxes or radio buttons, you have a choice. Of course, you can use this with radio buttons as well. It should be used, however, only when your logic is general enough to support this approach. Otherwise you'll end up with a cascaded if statement, a sure sign that you should revert to using independent listener classes. Lists You'll recall that one of the problems with the Java 1.0 List design is that it took extra work to make it do what you'd expect: react to a single click on one of the list elements. You just attach a listener like you do everywhere else. Menus The event handling for menus does seem to benefit from the Java 1.1 event model, but Java's approach to menus is still messy and requires a lot of hand coding. The right medium for a menu seems to be a resource rather than a lot of code. Keep in mind that program-building tools will generally handle the creation of menus for you, so that will reduce the pain somewhat (as long as they will also handle the maintenance!). In addition, you'll find the events for menus are inconsistent and can lead to confusion: MenuItems use ActionListeners, but CheckboxMenuItems use ItemListeners. The Menu objects can also support ActionListeners, but that's not usually helpful. In general, you'll attach listeners to each MenuItem or CheckboxMenuItem, but the following example (revised from the earlier version) also shows ways to combine the capture of multiple menu components into a single listener class. As you'll see, it's probably not worth the hassle to do this. Here you can see the ItemListeners and ActionListeners attached to the various menu components. Java 1.1 supports menu shortcuts, so you can select a menu item using the keyboard instead of the mouse. These are quite simple; you just use the overloaded MenuItem constructor that takes as a second argument a MenuShortcut object. The constructor for MenuShortcut takes the key of interest, which magically appears on the menu item when it drops down. The example above adds Control-E to the Exit menu item. You can also see the use of setActionCommand( ). This seems a bit strange because in each case the action command is exactly the same as the label on the menu component. Why not just use the label instead of this alternative string? The problem is internationalization. If you retarget this program to another language, you want to change only the label in the menu, and not go through the code changing all the logic that will no doubt introduce new errors. So to make this easy for code that checks the text string associated with a menu component, the action command can be immutable while the menu label can change. All the code works with the action command, so it's unaffected by changes to the menu labels. Note that in this program, not all the menu components are examined for their action commands, so those that aren't don't have their action command set. Much of the constructor is the same as before, with the exception of a couple of calls to add listeners. The bulk of the work happens in the listeners. In BL, the MenuBar swapping happens as in the previous example. In ML, the figure out who rang approach is taken by getting the source of the ActionEvent and casting it to a MenuItem, then getting the action command string to pass it through a cascaded if statement. Much of this is the same as before, but notice that if Exit is chosen, a new WindowEvent is created, passing in the handle of the enclosing class object (MenuNew.this) and creating a WINDOW_CLOSING event. Through this mechanism, you can dispatch any message you want in any circumstances, so it's quite powerful. The FL listener is simple even though it's handling all the different flavors in the flavor menu. Even with the profusion of classes generated this way, the code inside tends to be smaller and the process is more foolproof. Dialog boxes This is a direct rewrite of the earlier ToeTest.java. In this version, however, everything is placed inside an inner class. Although this completely eliminates the need to keep track of the object that spawned any class, as was the case in ToeTest.java, it could be taking the concept of inner classes a bit too far. At one point, the inner classes are nested four deep! This is the kind of design in which you need to decide whether the benefit of inner classes is worth the increased complexity. In addition, when you create a non-static inner class you're tying that class to its surrounding class. Sometimes a standalone class can more easily be reused. Binding events dynamically One of the benefits of the new AWT event model is flexibility. In the old model you were forced to hard code the behavior of your program, but with the new model you can add and remove event behavior with single method calls. Usually, components handle events as multicast, meaning that you can register many listeners for a single event. In the special components in which an event is handled as unicast, you'll get a TooManyListenersException. 2. During the execution of the program, listeners are dynamically added and removed from the Button b2. Adding is accomplished in the way you've seen before, but each component also has a removeXXXListener( ) method to remove each type of listener. This kind of flexibility provides much greater power in your programming. You should notice that event listeners are not guaranteed to be called in the order they are added (although most implementations do in fact work that way). It's much more desirable to separate your business logic from the GUI. This way, you can not only reuse the business logic more easily, it's also easier to reuse the GUI. Another issue is multi-tiered systems, where the business objects reside on a completely separate machine. This central location of the business rules allows changes to be instantly effective for all new transactions, and is thus a compelling way to set up a system. However, these business objects can be used in many different applications and so should not be tied to any particular mode of display. They should just perform the business operations and nothing more. It just does its job. Separation keeps track of all the UI details, and it talks to BusinessLogic only through its public interface. All the operations are centered around getting information back and forth through the UI and the BusinessLogic object. So Separation, in turn, just does its job. Since Separation knows only that it's talking to a BusinessLogic object (that is, it isn't highly coupled), it could be massaged into talking to other types of objects without much trouble. Thinking in terms of separating UI from business logic also makes life easier when you're adapting legacy code to work with Java. Recommended coding approaches Inner classes, the new event model, and the fact that the old event model is still supported along with new library features that rely on old-style programming has added a new element of confusion. Now there are even more different ways for people to write unpleasant code. Unfortunately, this kind of code is showing up in books and article examples, and even in documentation and examples distributed from Sun! Since this is also the simplest and clearest approach, it should be a relief for you to learn this. Before looking at anything else, you should know that although Java 1.1 is backward-compatible with Java 1.0 (that is, you can compile and run 1.0 programs with 1.1), you cannot mix the event models within the same program. That is, you cannot use the old-style action( ) method in the same program in which you employ listeners. This can be a problem in a larger program when you're trying to integrate old code with a new program, since you must decide whether to use the old, hard-to-maintain approach with the new program or to update the old code. This shouldn't be too much of a battle since the new approach is so superior to the old. Baseline: the good way to do it To give you something to compare with, here's an example showing the recommended approach. But notice that there isn't an if statement in the entire program, or any statement that says, I wonder what caused this event. Each piece of code is concerned with doing, not type-checking. This is the best way to write your code; not only is it easier to conceptualize, but much easier to read and maintain. Cutting and pasting to create new programs is also much easier. Implementing the main class as a listener The first bad idea is a common and recommended approach. This makes the main class (typically Applet or Frame, but it could be any class) implement the various listeners. However: 1. Java 1.1 supports JAR files so all your files can be placed in a single compressed JAR archive that requires only one server hit. You no longer need to reduce class count for Internet efficiency. 2. The code above is much less modular so it's harder to grab and paste. Note that you must not only implement the various interfaces for your main class, but in actionPerformed( ) you've got to detect which action was performed using a cascaded if statement. Not only is this going backwards, away from the listener model, but you can't easily reuse the actionPerformed( ) method since it's specific to this particular application. Contrast this with GoodIdea.java, in which you can just grab one listener class and paste it in anywhere else with minimal fuss. Plus you can register multiple listener classes with a single event, allowing even more modularity in what each listener class does. Mixing the approaches The second bad idea is to mix the two approaches: use inner listener classes, but also implement one or more listener interfaces as part of the main class. This approach has appeared without explanation in books and documentation, and I can only assume that the authors thought they must use the different approaches for different purposes. But you don't - in your programming you can probably use inner listener classes exclusively. It's also messier and less pleasant to read than the inner class approach. There's no reason that you have to use any of the old thinking for events in Java 1.1 - so why do it? Inheriting a component Another place where you'll often see variations on the old way of doing things is when creating a new type of component. The class Display is a way to centralize that information display. There's an array of Strings to hold information about each type of event, and the method show( ) takes a handle to whatever Graphics object you have and writes directly on that surface. The scheme is intended to be somewhat reusable. EnabledPanel represents the new type of component. The only reason for using this method is that it captures every event that happens, so you can view everything that goes on. On the other hand, the inner listener classes already know precisely what event occurred. Each listener modifies the Display string associated with its particular event and calls repaint( ) so the strings get displayed. The default version of update clears the background and then calls paint( ) to redraw any graphics. This clearing is usually what causes flicker but is not necessary since paint( ) redraws the entire surface. You can see that there are a lot of listeners - however, type checking occurs for the listeners, and you can't listen for something that the component doesn't support (unlike BadTechnique.java, which you will see momentarily). Experimenting with this program is quite educational since you learn a lot about the way that events occur in Java. A solution to this is to use mousePressed( ) and mouseReleased( ) instead of mouseClicked( ), and then determine whether to call your own mouseReallyClicked( ) method based on time and about 4 pixels of mouse hysteresis. Ugly component inheritance The alternative, which you will see put forward in many published works, is to call enableEvents( ) and pass it the masks corresponding to the events you want to handle. This causes those events to be sent to the old-style methods (although they're new to Java 1.1) with names like processFocusEvent( ). You must also remember to call the base-class version. But it's ugly and hard to write, read, debug, maintain, and reuse. So why bother when you can use inner listener classes? Java 1.1 UI APIs Java 1.1 has also added some important new functionality, including focus traversal, desktop color access, printing inside the sandbox, and the beginnings of clipboard support. Focus traversal is quite easy, since it's transparently present in the AWT library components and you don't have to do anything to make it work. If you make your own components and want them to handle focus traversal, you override isFocusTraversable( ) to return true. If you want to capture the keyboard focus on a mouse click, you catch the mouse down event and call requestFocus( ). Desktop colors The desktop colors provide a way for you to know what the various color choices are on the current user's desktop. This way, you can use those colors in your program if you desire. The colors are automatically initialized and placed in static members of class SystemColor, so all you need to do is read the member you're interested in. Printing Unfortunately, there isn't much that's automatic with printing. Instead you must go through a number of mechanical, non-OO steps in order to print. Printing a component graphically can be slightly more automatic: by default, the print( ) method calls paint( ) to do its work. There are times when this is satisfactory, but if you want to do anything more specialized you must know that you're printing so you can in particular find out the page dimensions. The following example demonstrates the printing of both text and graphics, and the different approaches you can use for printing graphics. It uses these to print out text in bold, italic, and in different sizes. In addition, a new type of component called a Plot is created to demonstrate graphics. A Plot has rings that it will display on the screen and print onto paper, and the three derived classes Plot1, Plot2, and Plot3 perform these tasks in different ways so that you can see your alternatives when printing graphics. This might be fixed in a future release of Java, and you can use this program to test it. This program encapsulates functionality inside inner classes whenever possible, to facilitate reuse. For example, whenever you want to begin a print job (whether for graphics or text), you must create a PrintJob object, which has its own Graphics object along with the width and height of the page. The creation of a PrintJob and extraction of page dimensions is encapsulated in the PrintData class. Printing text Conceptually, printing text is straightforward: you choose a typeface and size, decide where the string should go on the page, and draw it with Graphics.drawString( ). This means, however, that you must perform the calculations of exactly where each line will go on the page to make sure it doesn't run off the end of the page or collide with other lines. If you want to make a word processor, your work is cut out for you. It also simplifies the calculation of the width and height of a string. When you press the Print text button, the TBL listener is activated. You can see that it goes through two iterations of creating a ChangeFont object and calling drawString( ) to print out the string in a calculated position, centered, one-third, and two-thirds down the page, respectively. Notice whether these calculations produce the expected results. The creation of a PrintData object initializes g, and then you simply call print( ) for the component you want to print. To force printing you must call dispose( ) for the Graphics object and end( ) for the PrintData object (which turns around and calls end( ) for the PrintJob). The work is going on inside the Plot object. You can see that the base-class Plot is simple - it extends Canvas and contains an int called rings to indicate how many concentric rings to draw on this particular Canvas. The three derived classes show different approaches to accomplishing the same goal: drawing on both the screen and on the printed page. Plot1 takes the simplest approach to coding: ignore the fact that there are differences in painting and printing, and just override paint( ). The reason this works is that the default print( ) method simply turns around and calls paint( ). However, you'll notice that the size of the output depends on the size of the on-screen canvas, which makes sense since the width and height are determined by calling Canvas.getSize( ). The other situation in which this is acceptable is if your image is always a fixed size. When the size of the drawing surface is important, then you must discover the dimensions. Unfortunately, this turns out to be awkward, as you can see in Plot2. For some possibly good reason that I don't know, you cannot simply ask the Graphics object the dimensions of its drawing surface. This would have made the whole process quite elegant. Instead, to see if you're printing rather than painting, you must detect the PrintGraphics using the RTTI instanceof keyword (described in Chapter 11), then downcast and call the sole PrintGraphics method: getPrintJob( ). Now you have a handle to the PrintJob and you can find out the width and height of the paper. This is a hacky approach, but perhaps there is some rational reason for it. But since the print( ) method should be called when printing, why not use that? This approach is used in Plot3, and it eliminates the need to use instanceof since inside print( ) you can assume that you can cast to a PrintGraphics object. This is a little better. The situation is improved by placing the common drawing code (once the dimensions have been detected) inside a separate method doGraphics( ). Running Frames within applets What if you'd like to print from within an applet? Well, to print anything you must get a PrintJob object through a Toolkit object's getPrintJob( ) method, which takes only a Frame object and not an Applet. Thus it would seem that it's possible to print from within an application, but not an applet. This is a useful technique since it allows you to use many applications within applets (as long as they don't violate applet security). Some of the publicity seemed to claim that you'd be able to print from within an applet. However, the Java security system contains a feature that could lock out an applet from initiating its own print job, requiring that the initiation be done via a Web browser or applet viewer. At the time of this writing, this seemed to remain an unresolved issue. When I ran this program from within a Web browser, the PrintDemo window came up just fine, but it wouldn't print from the browser. The clipboard Java 1.1 supports limited operations with the system clipboard (in the java.awt.datatransfer package). You can copy String objects to the clipboard as text, and you can paste text from the clipboard into String objects. Of course, the clipboard is designed to hold any type of data, but how this data is represented on the clipboard is up to the program doing the cutting and pasting. The following program is a simple demonstration of cut, copy, and paste with String data in a TextArea. One thing you'll notice is that the keyboard sequences you normally use for cutting, copying, and pasting also work. But if you look at any TextField or TextArea in any other program you'll find that they also automatically support the clipboard key sequences. This example simply adds programmatic control of the clipboard, and you could use these techniques if you want to capture clipboard text into some non-TextComponent. What's different is the creation of the Clipboard field clipbd, which is done through the Toolkit. All the action takes place in the listeners. The CopyL and CutL listeners are the same except for the last line of CutL, which erases the line that's been copied. The special two lines are the creation of a StringSelection object from the String and the call to setContents( ) with this StringSelection. That's all there is to putting a String on the clipboard. In PasteL, data is pulled off the clipboard using getContents( ). What comes back is a fairly anonymous Transferable object, and you don't really know what it contains. One way to find out is to call getTransferDataFlavors( ), which returns an array of DataFlavor objects indicating which flavors are supported by this particular object. You can also ask it directly with isDataFlavorSupported( ), passing in the flavor you're interested in. Here, however, the bold approach is taken: getTransferData( ) is called assuming that the contents supports the String flavor, and if it doesn't the problem is sorted out in the exception handler. In the future you can expect more data flavors to be supported. Visual programming and Beans So far in this book you've seen how valuable Java is for creating reusable pieces of code. The most reusable unit of code has been the class, since it comprises a cohesive unit of characteristics (fields) and behaviors (methods) that can be reused either directly via composition or through inheritance. Inheritance and polymorphism are essential parts of object-oriented programming, but in the majority of cases when you're putting together an application, what you really want is components that do exactly what you need. You'd like to drop these parts into your design like the electronic engineer puts together chips on a circuit board (or even, in the case of Java, onto a Web page). It seems, too, that there should be some way to accelerate this modular assembly style of programming. Visual programming first became successful - very successful - with Microsoft's Visual Basic (VB), followed by a second-generation design in Borland's Delphi (the primary inspiration for the Java Beans design). With these programming tools the components are represented visually, which makes sense since they usually display some kind of visual component such as a button or a text field. The visual representation, in fact, is often exactly the way the component will look in the running program. So part of the process of visual programming involves dragging a component from a pallet and dropping it onto your form. The application builder tool writes code as you do this, and that code will cause the component to be created in the running program. Simply dropping the component onto a form is usually not enough to complete the program. Often, you must change the characteristics of a component, such as what color it is, what text is on it, what database it's connected to, etc. Characteristics that can be modified at design time are referred to as properties. You can manipulate the properties of your component inside the application builder tool, and when you create the program this configuration data is saved so that it can be rejuvenated when the program is started. By now you're probably used to the idea that an object is more than characteristics; it's also a set of behaviors. Here's the critical part: the application builder tool is able to dynamically interrogate (using reflection) the component to find out which properties and events the component supports. Once it knows what they are, it can display the properties and allow you to change those (saving the state when you build the program), and also display the events. In general, you do something like double clicking on an event and the application builder tool creates a code body and ties it to that particular event. All you have to do at that point is write the code that executes when the event occurs. All this adds up to a lot of work that's done for you by the application builder tool. As a result you can focus on what the program looks like and what it is supposed to do, and rely on the application builder tool to manage the connection details for you. The reason that visual programming tools have been so successful is that they dramatically speed up the process of building an application - certainly the user interface, but often other portions of the application as well. What is a Bean? After the dust settles, then, a component is really just a block of code, typically embodied in a class. The key issue is the ability for the application builder tool to discover the properties and events for that component. To create a VB component, the programmer had to write a fairly complicated piece of code following certain conventions to expose the properties and events. Delphi was a second-generation visual programming tool and the language was actively designed around visual programming so it is much easier to create a visual component. However, Java has brought the creation of visual components to its most advanced state with Java Beans, because a Bean is just a class. You don't have to write any extra code or use special language extensions in order to make something a Bean. The only thing you need to do, in fact, is slightly modify the way that you name your methods. It is the method name that tells the application builder tool whether this is a property, an event, or just an ordinary method. In the Java documentation, this naming convention is mistakenly termed a design pattern. This is unfortunate since design patterns (see Chapter 16) are challenging enough without this sort of confusion. It's not a design pattern, it's just a naming convention and it's fairly simple: 1. For a property named xxx, you typically create two methods: getXxx( ) and setXxx( ). Note that the first letter after get or set is automatically lowercased to produce the property name. The type produced by the get method is the same as the type of the argument to the set method. The name of the property and the type for the get and set are not related. 2. For a boolean property, you can use the get and set approach above, but you can also use is instead of get. 3. Ordinary methods of the Bean don't conform to the above naming convention, but they're public. 4. For events, you use the listener approach. It's exactly the same as you've been seeing: addFooBarListener(FooBarListener) and removeFooBarListener(FooBarListener) to handle a FooBarEvent. Most of the time the built-in events and listeners will satisfy your needs, but you can also create your own events and listener interfaces. Point 1 above answers a question about something you might have noticed in the change from Java 1.0 to Java 1.1: a number of method names have had small, apparently meaningless name changes. Now you can see that most of those changes had to do with adapting to the get and set naming conventions in order to make that particular component into a Bean. Usually, all your fields will be private, and accessible only through methods. Following the naming convention, the properties are jumps, color, spots, and jumper (notice the change in case of the first letter in the property name). The events this Bean handles are ActionEvent and KeyEvent, based on the naming of the add and remove methods for the associated listener. Finally, you can see that the ordinary method croak( ) is still part of the Bean simply because it's a public method, not because it conforms to any naming scheme. Extracting BeanInfo with the Introspector One of the most critical parts of the Bean scheme occurs when you drag a Bean off a palette and plop it down on a form. Part of the solution is already evident from the end of Chapter 11: Java 1.1 reflection allows all the methods of an anonymous class to be discovered. This is perfect for solving the Bean problem without requiring you to use any extra language keywords like those required in other visual programming languages. In fact, one of the prime reasons that reflection was added to Java 1.1 was to support Beans (although reflection also supports object serialization and remote method invocation). So you might expect that the creator of the application builder tool would have to reflect each Bean and hunt through its methods to find the properties and events for that Bean. This is certainly possible, but the Java designers wanted to provide a standard interface for everyone to use, not only to make Beans simpler to use but also to provide a standard gateway to the creation of more complex Beans. This interface is the Introspector class, and the most important method in this class is the static getBeanInfo( ). You pass a Class handle to this method and it fully interrogates that class and returns a BeanInfo object that you can then dissect to find properties, methods, and events. Usually you won't care about any of this - you'll probably get most of your Beans off the shelf from vendors, and you don't need to know all the magic that's going on underneath. You'll simply drag your Beans onto your form, then configure their properties and write handlers for the events you're interested in. First it tries to create a BeanInfo object, and if successful calls the methods of BeanInfo that produce information about properties, methods, and events. In Introspector.getBeanInfo( ), you'll see there is a second argument. This tells the Introspector where to stop in the inheritance hierarchy. Here, it stops before it parses all the methods from Object, since we're not interested in seeing those. For properties, getPropertyDescriptors( ) returns an array of PropertyDescriptors. For each PropertyDescriptor you can call getPropertyType( ) to find the class of object that is passed in and out via the property methods. Then, for each property you can get its pseudonym (extracted from the method names) with getName( ), the method for reading with getReadMethod( ), and the method for writing with getWriteMethod( ). These last two methods return a Method object that can actually be used to invoke the corresponding method on the object (this is part of reflection). For the public methods (including the property methods), getMethodDescriptors( ) returns an array of MethodDescriptors. For each one you can get the associated Method object and print out its name. For the events, getEventSetDescriptors( ) returns an array of (what else?) EventSetDescriptors. Each of these can be queried to find out the class of the listener, the methods of that listener class, and the add- and remove-listener methods. The BeanDumper program prints out all of this information. You can see that the type of the property and its name are independent. Notice the lowercasing of the property name. The public method list includes the methods that are not associated with a property or event, such as croak( ), as well as those that are. These are all the methods that you can call programmatically for a Bean, and the application builder tool can choose to list all of these while you're making method calls, to ease your task. Finally, you can see that the events are fully parsed out into the listener, its methods, and the add- and remove-listener methods. Basically, once you have the BeanInfo, you can find out everything of importance for the Bean. You can also call the methods for that Bean, even though you don't have any other information except the object (again, a feature of reflection). A more sophisticated Bean This next example is slightly more sophisticated, albeit frivolous. It's a canvas that draws a little circle around the mouse whenever the mouse is moved. When you press the mouse, the word Bang! appears in the middle of the screen, and an action listener is fired. The properties you can change are the size of the circle as well as the color, size, and text of the word that is displayed when you press the mouse. A BangBean also has its own addActionListener( ) and removeActionListener( ) so you can attach your own listener that will be fired when the user clicks on the BangBean. This means that the application builder tool can pickle all the information for the BangBean using serialization after the program designer has adjusted the values of the properties. When the Bean is created as part of the running application, these pickled properties are restored so that you get exactly what you designed. You can see that all the fields are private, which is what you'll usually do with a Bean - allow access only through methods, usually using the property scheme. When you look at the signature for addActionListener( ), you'll see that it can throw a TooManyListenersException. This indicates that it is unicast, which means it notifies only one listener when the event occurs. Ordinarily, you'll use multicast events so that many listeners can be notified of an event. However, that runs into issues that you won't be ready for until the next chapter, so it will be revisited there (under the heading Java Beans revisited). A unicast event sidesteps the problem. When you press the mouse, the text is put in the middle of the BangBean, and if the actionListener field is not null, its actionPerformed( ) is called, creating a new ActionEvent object in the process. Whenever the mouse is moved, its new coordinates are captured and the canvas is repainted (erasing any text that's on the canvas, as you'll see). The main( ) is added to allow you to test the program from the command line. When a Bean is in a development environment, main( ) will not be used, but it's helpful to have a main( ) in each of your Beans because it provides for rapid testing. Usually, of course, the application builder tool would create most of the code that uses the Bean. When you run the BangBean through BeanDumper or put the BangBean inside a Bean-enabled development environment, you'll notice that there are many more properties and actions than are evident from the above code. That's because BangBean is inherited from Canvas, and Canvas is a Bean, so you're seeing its properties and events as well. The only tricky part is that you must make sure that you get the proper path in the Name: field. If you look back at BangBean.java, you'll see it's in package bangbean (and thus in a subdirectory called bangbean that's off of the classpath), and the name in the manifest file must include this package information. In addition, you must place the manifest file in the directory above the root of your package path, which in this case means placing the file in the directory above the bangbean subdirectory. When you give jar the name of a subdirectory, it packages that entire subdirectory into the jar file (including, in this case, the original BangBean.java source-code file - you might not choose to include the source with your own Beans). You can also add other Beans to the JAR file simply by adding their information to your manifest. One thing to notice is that you'll probably want to put each Bean in its own subdirectory, since when you create a JAR file you hand the jar utility the name of a subdirectory and it puts everything in that subdirectory into the JAR file. You can see that both Frog and BangBean are in their own subdirectories. Once you have your Bean properly inside a JAR file you can bring it into a Beans-enabled program-builder environment. More complex Bean support You can see how remarkably simple it is to make a Bean. But you aren't limited to what you've seen here. The Java Bean design provides a simple point of entry but can also scale to more complex situations. These situations are beyond the scope of this book but they will be briefly introduced here. One place where you can add sophistication is with properties. The examples above have shown only single properties, but it's also possible to represent multiple properties in an array. This is called an indexed property. You simply provide the appropriate methods (again following a naming convention for the method names) and the Introspector recognizes an indexed property so your application builder tool can respond appropriately. Properties can be bound, which means that they will notify other objects via a PropertyChangeEvent. The other objects can then choose to change themselves based on the change to the Bean. Properties can be constrained, which means that other objects can veto a change to that property if it is unacceptable. The other objects are notified using a PropertyChangeEvent, and they can throw a ProptertyVetoException to prevent the change from happening and to restore the old values. You can also change the way your Bean is represented at design time: 1. You can provide a custom property sheet for your particular Bean. The ordinary property sheet will be used for all other Beans, but yours is automatically invoked when your Bean is selected. 2. You can create a custom editor for a particular property, so the ordinary property sheet is used, but when your special property is being edited, your editor will automatically be invoked. 3. You can provide a custom BeanInfo class for your Bean that produces information that's different from the default created by the Introspector. 4. It's also possible to turn expert mode on and off in all FeatureDescriptors to distinguish between basic features and more complicated ones. More to Beans There's another issue that couldn't be addressed here. Whenever you create a Bean, you should expect that it will be run in a multithreaded environment. This means that you must understand the issues of threading, which will be introduced in the next chapter. You'll find a section there called Java Beans revisited that will look at the problem and its solution. Sure, there's now a good event model, and JavaBeans is an excellent component-reuse design. But the GUI components still seem rather minimal, primitive, and awkward. That's where Swing comes in. The Swing library appeared after Java 1.1 so you might naturally assume that it's part of Java 1.2. However, it is designed to work with Java 1.1 as an add-on. This way, you don't have to wait for your platform to support Java 1.2 in order to enjoy a good UI component library. Your users might actually need to download the Swing library if it isn't part of their Java 1.1 support, and this could cause a few snags. But it works. Swing contains all the components that you've been missing throughout the rest of this chapter: those you expect to see in a modern UI, everything from buttons that contain pictures to trees and grids. It's a big library, but it's designed to have appropriate complexity for the task at hand - if something is simple, you don't have to write much code but as you try to do more your code becomes increasingly complex. This means an easy entry point, but you've got the power if you need it. Swing has great depth. This section does not attempt to be comprehensive, but instead introduces the power and simplicity of Swing to get you started using the library. Please be aware that what you see here is intended to be simple. If you need to do more, then Swing can probably give you what you want if you're willing to do the research by hunting through the online documentation from Sun. Benefits of Swing When you begin to use the Swing library, you'll see that it's a huge step forward. Swing components are Beans (and thus use the Java 1.1 event model), so they can be used in any development environment that supports Beans. Swing provides a full set of UI components. For speed, all the components are lightweight (no peer components are used), and Swing is written entirely in Java for portability. Much of what you'll like about Swing could be called orthogonality of use; that is, once you pick up the general ideas about the library you can apply them everywhere. Primarily because of the Beans naming conventions, much of the time I was writing these examples I could guess at the method names and get it right the first time, without looking anything up. This is certainly the hallmark of a good library design. In addition, you can generally plug components into other components and things will work correctly. Keyboard navigation is automatic - you can use a Swing application without the mouse, but you don't have to do any extra programming (the old AWT required some ugly code to achieve keyboard navigation). Scrolling support is effortless - you simply wrap your component in a JScrollPane as you add it to your form. Other features such as tool tips typically require a single line of code to implement. Swing also supports something called pluggable look and feel, which means that the appearance of the UI can be dynamically changed to suit the expectations of users working under different platforms and operating systems. It's even possible to invent your own look and feel. Easy conversion If you've struggled long and hard to build your UI using Java 1.1, you don't want to throw it away to convert to Swing. Fortunately, the library is designed to allow easy conversion - in many cases you can simply put a 'J' in front of the class names of each of your old AWT components. Also, you don't just add( ) something to a Swing JFrame, but you must get the content pane first, as seen above. But you can easily get many of the benefits of Swing with a simple conversion. Because of the package statement, you'll have to invoke this program by saying: java c13.swing.JbuttonDemo All of the programs in this section will require a similar form to run them. A display framework Although the programs that are both applets and applications can be valuable, if used everywhere they become distracting and waste paper. Finally, they create a main( ) containing the line: Show.inFrame(new MyClass(), 500, 300); in which the last two arguments are the display width and height. Note that the title for the JFrame is produced using RTTI. Tool tips Almost all of the classes that you'll be using to create your user interfaces are derived from JComponent, which contains a method called setToolTipText(String). Borders JComponent also contains a method called setBorder( ), which allows you to place various interesting borders on any visible component. The following example demonstrates a number of the different borders that are available, using a method called showBorder( ) that creates a JPanel and puts on the border in each case. You can also create your own borders and put them inside buttons, labels, etc. In com.sun.java.swing.basic, there is a BasicArrowButton that is convenient, but what to test it on? There are two types of spinners that just beg to be used with arrow buttons: Spinner, which changes an int value, and StringSpinner, which moves through an array of String (even automatically wrapping when it reaches the end of the array). The ActionListeners attached to the arrow buttons shows how relatively obvious it is to use these spinners: you just get and set values, using method names you would expect since they're Beans. When you run the example, you'll see that the toggle button holds its last position, in or out. But the check boxes and radio buttons behave identically to each other, just clicking on or off (they are inherited from JToggleButton). Button groups If you want radio buttons to behave in an exclusive or fashion, you must add them to a button group, in a similar but less awkward way as the old AWT. But as the example below demonstrates, any AbstractButton can be added to a ButtonGroup. To avoid repeating a lot of code, this example uses reflection to generate the groups of different types of buttons. The AbstractButton is initialized to a JButton that has the label Failed so if you ignore the exception message, you'll still see the problem on screen. The getConstructor( ) method produces a Constructor object that takes the array of arguments of the types in the Class array passed to getConstructor( ). Then all you do is call newInstance( ), passing it an array of Object containing your actual arguments - in this case, just the String from the ids array. This adds a little complexity to what is a simple process. To get exclusive or behavior with buttons, you create a button group and add each button for which you want that behavior to the group. When you run the program, you'll see that all the buttons except JButton exhibit this exclusive or behavior. Icons You can use an Icon inside a JLabel or anything that inherits from AbstractButton (including JButton, JCheckbox, JradioButton, and the different kinds of JMenuItem). Using Icons with JLabels is quite straightforward (you'll see an example later). The following example explores all the additional ways you can use Icons with buttons and their descendants. You can use any gif files you want, but the ones used in this example are part of the book's code distribution, available at www.BruceEckel.com. To open a file and bring in the image, simply create an ImageIcon and hand it the file name. From then on, you can use the resulting Icon in your program. You'll see that this gives the button a rather animated feel. Note that a tool tip is also added to the button. Menus Menus are much improved and more flexible in Swing - for example, you can use them just about anywhere, including panels and applets. In addition, menu code gets long-winded and sometimes messy. The following approach takes a step in the direction of solving this problem by putting all the information about each menu into a two-dimensional array of Object (that way you can put anything you want into the array). This array is organized so that the first row represents the menu name, and the remaining rows represent the menu items and their characteristics. You'll notice the rows of the array do not have to be uniform from one to the next - as long as your code knows where everything should be, each row can be completely different. Each table produces one menu, and the first row in the table contains the menu name and its keyboard accelerator. If a row starts with null it is treated as a separator. Remember that an array of Object may hold only Object handles and not primitive values. This example also shows how JLabels and JMenuItems (and their descendants) may hold Icons. An Icon is placed into the JLabel via its constructor and changed when the corresponding menu item is selected. The menuBar array contains the handles to all the file menus in the order that you want them to appear on the menu bar. You pass this array to createMenuBar( ), which breaks it up into individual arrays of menu data, passing each to createMenu( ). This method, in turn, takes the first line of the menu data and creates a JMenu from it, then calls createMenuItem( ) for each of the remaining lines of menu data. Finally, createMenuItem( ) parses each line of menu data and determines the type of menu and its attributes, and creates that menu item appropriately. In the end, as you can see in the Menus( ) constructor, to create a menu from these tables say createMenuBar(menuBar) and everything is handled recursively. This example does not take care of building cascading menus, but you should have enough of the concept that you can add that capability if you need it. Popup menus The implementation of JPopupMenu seems a bit strange: you must call enableEvents( ) and select for mouse events instead of using an event listener. List boxes and combo boxes List boxes and combo boxes in Swing work much as they do in the old AWT, but they also have increased functionality if you need it. In addition, some conveniences have been added. For example, the JList has a constructor that takes an array of Strings to display (oddly enough this same feature is not available in JComboBox). Adding support for scrolling turns out to be quite easy, as shown above - you simply wrap the JList in a JScrollPane and all the details are automatically managed for you. Sliders and progress bars A slider allows the user to input data by moving a point back and forth, which is intuitive in some situations (volume controls, for example). A progress bar displays data in a relative fashion from full to empty so the user gets a perspective. Notice how straightforward it is to add a titled border. The API for trees is vast, however - certainly one of the largest in Swing. It appears that you can do just about anything with trees, but more sophisticated tasks might require quite a bit of research and experimentation. Fortunately, there is a middle ground provided in the library: the default tree components, which generally do what you need. So most of the time you can use these components, and only in special cases will you need to delve in and understand trees more deeply. The following example uses the default tree components to display a tree in an applet. Then node( ) can be called to produce the root of this branch. The Trees class contains a two-dimensional array of Strings from which Branches can be made and a static int i to count through this array. The DefaultMutableTreeNode objects hold the nodes, but the physical representation on screen is controlled by the JTree and its associated model, the DefaultTreeModel. Note that when the JTree is added to the applet, it is wrapped in a JScrollPane - this is all it takes to provide automatic scrolling. The JTree is controlled through its model. When you make a change to the model, the model generates an event that causes the JTree to perform any necessary updates to the visible representation of the tree. In init( ), the model is captured by calling getModel( ). When the button is pressed, a new branch is created. Then the currently selected component is found (or the root if nothing is selected) and the model's insertNodeInto( ) method does all the work of changing the tree and causing it to be updated. Most of the time an example like the one above will give you what you need in a tree. However, trees have the power to do just about anything you can imagine - everywhere you see the word default in the example above, you can substitute your own class to get different behavior. But beware: almost all of these classes have a large interface, so you could spend a lot of time struggling to understand the intricacies of trees. Tables Like trees, tables in Swing are vast and powerful. They are primarily intended to be the popular grid interface to databases via Java Database Connectivity (JDBC, discussed in Chapter 15) and thus they have a tremendous amount of flexibility, which you pay for in complexity. There's easily enough here to be the basis of a full-blown spreadsheet and could probably justify an entire book. However, it is also possible to create a relatively simple JTable if you understand the basics. The JTable controls how the data is displayed, but the TableModel controls the data itself. So to create a JTable you'll typically create a TableModel first. The constructor adds a TableModelListener which prints the array every time the table is changed. The rest of the methods follow the Beans naming convention, and are used by JTable when it wants to present the information in DataModel. AbstractTableModel provides default methods for setValueAt( ) and isCellEditable( ) that prevent changes to the data, so if you want to be able to edit the data, you must override these methods. Once you have a TableModel, you only need to hand it to the JTable constructor. All the details of displaying, editing and updating will be taken care of for you. Notice that this example also puts the JTable in a JScrollPane, which requires a special JTable method. Tabbed Panes Earlier in this chapter you were introduced to the positively medieval CardLayout, and saw how you had to manage all the switching of the ugly cards yourself. Someone actually thought this was a good design. Fortunately, Swing remedies this by providing JTabbedPane, which handles all the tabs, the switching, and everything. The contrast between CardLayout and JTabbedPane is breathtaking. The following example is quite fun because it takes advantage of the design of the previous examples. They are all built as descendants of JPanel, so this example will place each one of the previous examples in its own pane on a JTabbedPane. In the Tabbed( ) constructor, you can see the two important JTabbedPane methods that are used: addTab( ) to put a new pane in, and setSelectedIndex( ) to choose the pane to start with. The Component will be displayed in the pane. Once you do this, no further management is necessary - the JTabbedPane takes care of everything else for you (as it should). It adds a TitledBorder that contains the name of the class and returns the result as a JPanel to be used in addTab( ). When you run the program you'll see that the JTabbedPane automatically stacks the tabs if there are too many of them to fit on one row. The Swing message box Windowing environments commonly contain a standard set of message boxes that allow you to quickly post information to the user or to capture information from the user. In Swing, these message boxes are contained in JOptionPane. You have many different possibilities (some quite sophisticated), but the ones you'll most commonly use are probably the message dialog and confirmation dialog, invoked using the static JOptionPane.showMessageDialog( ) and JOptionPane. More to Swing This section was meant only to give you an introduction to the power of Swing and to get you started so you could see how relatively simple it is to feel your way through the libraries. What you've seen so far will probably suffice for a good portion of your UI design needs. However, there's a lot more to Swing - it's intended to be a fully-powered UI design tool kit. If you don't see what you need here, delve into the online documentation from Sun and search the Web. There's probably a way to accomplish just about everything you can imagine. These are fairly straightforward to use. In many ways, these are like exceptions: the type is what's important, and the name can be used to infer just about everything else about them. When Java 1.1 introduced the new event model and Java Beans, the stage was set - now it was possible to create GUI components that could be easily dragged and dropped inside visual application builder tools. In addition, the design of the event model and Beans clearly shows strong consideration for ease of programming and maintainable code (something that was not evident in the 1.0 AWT). With the Swing components, cross-platform GUI programming can be a civilized experience. Actually, the only thing that's missing is the application builder tool, and this is where the real revolution lies. Microsoft's Visual Basic and Visual C++ require their application builder tools, as does Borland's Delphi and C++ Builder. If you want the application builder tool to get better, you have to cross your fingers and hope the vendor will give you what you want. But Java is an open environment, and so not only does it allow for competing application builder environments, it encourages them. And for these tools to be taken seriously, they must support Java Beans. This means a leveled playing field: if a better application builder tool comes along, you're not tied to the one you've been using - you can pick up and move to the new one and increase your productivity. This kind of competitive environment for GUI application builder tools has not been seen before, and the resulting competition can generate only positive results for the productivity of the programmer. Exercises 1. Create an applet with a text field and three buttons. When you press each button, make some different text appear in the text field. 2. Add a check box to the applet created in Exercise 1, capture the event, and insert different text into the text field. 3. Create an applet and add all the components that cause action( ) to be called, then capture their events and display an appropriate message for each inside a text field. 4. Add to Exercise 3 the components that can be used only with events detected by handleEvent( ). Override handleEvent( ) and display appropriate messages for each inside a text field. 5. Create an applet with a Button and a TextField. Write a handleEvent( ) so that if the button has the focus, characters typed into it will appear in the TextField. 6. Create an application and add to the main frame all the components described in this chapter, including menus and a dialog box. 7. Modify TextNew.java so that the characters in t2 retain the original case that they were typed in, instead of automatically being forced to upper case. 8. Modify CardLayout1.java so that it uses the Java 1.1 event model. 9. Add Frog.class to the manifest file shown in this chapter and run jar to create a JAR file containing both Frog and BangBean. Now either download and install the BDK from Sun or use your own Beans-enabled program builder tool and add the JAR file to your environment so you can test the two Beans. Often, you also need to turn a program into separate, independently-running subtasks. Each of these independent subtasks is called a thread, and you program as if each thread runs by itself and has the CPU to itself. Some underlying mechanism is actually dividing up the CPU time for you, but in general, you don't have to think about it, which makes programming with multiple threads a much easier task. Some definitions are useful at this point. A process is a self-contained running program with its own address space. A multitasking operating system is capable of running more than one process (program) at a time, while making it look like each one is chugging along by periodically providing CPU cycles to each process. A thread is a single sequential flow of control within a process. A single process can thus have multiple concurrently executing threads. There are many possible uses for multithreading, but in general, you'll have some part of your program tied to a particular event or resource, and you don't want to hang up the rest of your program because of that. So you create a thread associated with that event or resource and let it run independently of the main program. A good example is a quit button - you don't want to be forced to poll the quit button in every piece of code you write in your program and yet you want the quit button to be responsive, as if you were checking it regularly. In fact, one of the most immediately compelling reasons for multithreading is to produce a responsive user interface. Responsive user interfaces As a starting point, consider a program that performs some CPU-intensive operation and thus ends up ignoring user input and being unresponsive. The go( ) method is where the program stays busy: it puts the current value of count into the TextField t, then increments count. Part of the infinite loop inside go( ) is to call sleep( ). Note that sleep( ) can throw InterruptedException, although throwing such an exception is considered a hostile way to break from a thread and should be discouraged. When the start button is pressed, go( ) is invoked. And upon examining go( ), you might naively think (as I did) that it should allow multithreading because it goes to sleep. That is, while the method is asleep, it seems like the CPU could be busy monitoring other button presses. But it turns out that the real problem is that go( ) never returns, since it's in an infinite loop, and this means that actionPerformed( ) never returns. Since you're stuck inside actionPerformed( ) for the first keypress, the program can't handle any other events. But in a conventional method like go( ) it cannot continue and at the same time return control to the rest of the program. This sounds like an impossible thing to accomplish, as if the CPU must be in two places at once, but this is precisely the illusion that threading provides. The thread model (and programming support in Java) is a programming convenience to simplify juggling several operations at the same time within a single program. With threads, the CPU will pop around and give each thread some of its time. Each thread has the consciousness of constantly having the CPU to itself, but the CPU's time is actually sliced between all the threads. Threading reduces computing efficiency somewhat, but the net improvement in program design, resource balancing, and user convenience is often quite valuable. Of course, if you have more than one CPU, then the operating system can dedicate each CPU to a set of threads or even a single thread and the whole program can run much faster. Multitasking and multithreading tend to be the most reasonable ways to utilize multiprocessor systems. Inheriting from Thread The simplest way to create a thread is to inherit from class Thread, which has all the wiring necessary to create and run threads. The most important method for Thread is run( ), which you must override to make the thread do your bidding. Thus, run( ) is the code that will be executed simultaneously with the other threads in a program. The following example creates any number of threads that it keeps track of by assigning each thread a unique number, generated with a static variable. The Thread's run( ) method is overridden to count down each time it passes through its loop and to finish when the count is zero (at the point when run( ) returns, the thread is terminated). Often, run( ) is cast in the form of an infinite loop, which means that, barring some external call to stop( ) or destroy( ) for that thread, it will run forever (until the program completes). In main( ) you can see a number of threads being created and run. The special method that comes with the Thread class is start( ), which performs special initialization for the thread and then calls run( ). So the steps are: the constructor is called to build the object, then start( ) configures the thread and calls run( ). If you don't call start( ) (which you can do in the constructor, if that's appropriate) the thread will never be started. This shows that sleep( ), while it relies on the existence of a thread in order to execute, is not involved with either enabling or disabling threading. It's simply another method. You can also see that the threads are not run in the order that they're created. In fact, the order that the CPU attends to an existing set of threads is indeterminate, unless you go in and adjust the priorities using Thread's setPriority( ) method. When main( ) creates the Thread objects it isn't capturing the handles for any of them. An ordinary object would be fair game for garbage collection, but not a Thread. Each Thread registers itself so there is actually a reference to it someplace and the garbage collector can't clean it up. Threading for a responsive interface Now it's possible to solve the problem in Counter1.java with a thread. The trick is to place the subtask - that is, the loop that's inside go( ) - inside the run( ) method of a thread. But now, when the user presses the start button, a method is not called. Instead a thread of class SeparateSubTask is created (the constructor starts it, in this case), and then the Counter2 event loop continues. Note that the handle to the SeparateSubTask is stored so that when you press the onOff button it can toggle the runFlag inside the SeparateSubTask object. That thread (when it looks at the flag) can then start and stop itself. Because SeparateSubTask knows that it holds a handle to a Counter2, it can reach in and access Counter2's TextField when it needs to. When you press the onOff button, you'll see a virtually instant response. Of course, the response isn't really instant, not like that of a system that's driven by interrupts. The counter stops only when the thread has the CPU and notices that the flag has changed. Improving the code with an inner class As an aside, look at the coupling that occurs between the SeparateSubTask and Counter2 classes. The SeparateSubTask is intimately tied to Counter2 - it must keep a handle to its parent Counter2 object so it can call back and manipulate it. And yet the two classes shouldn't really merge together into a single class (although in the next section you'll see that Java provides a way to combine them) because they're doing separate things and are created at different times. They are tightly connected (what I call a couplet) and this makes the coding awkward. You can also see that the inner class is private, which means that its fields and methods can be given default access (except for run( ), which must be public since it is public in the base class). The private inner class is not accessible to anyone but Counter2i, and since the two classes are tightly coupled it's convenient to loosen the access restrictions between them. In SeparateSubTask you can see that the invertFlag( ) method has been removed since Counter2i can now directly access runFlag. Also, notice that SeparateSubTask's constructor has been simplified - now it only starts the thread. The handle to the Counter2i object is still being captured as in the previous version, but instead of doing it by hand and referencing the outer object by hand, the inner class mechanism takes care of it automatically. In run( ), you can see that t is simply accessed, as if it were a field of SeparateSubTask. Anytime you notice classes that appear to have high coupling with each other, consider the coding and maintenance improvements you might get by using inner classes. Combining the thread with the main class In the example above you can see that the thread class is separate from the program's main class. This makes a lot of sense and is relatively easy to understand. There is, however, an alternate form that you will often see used that is not so clear but is usually more concise (which probably accounts for its popularity). This form combines the main program class with the thread class by making the main program class a thread. Since for a GUI program the main program class must be inherited from either Frame or Applet, an interface must be used to paste on the additional functionality. This interface is called Runnable, and it contains the same basic method that Thread does. In fact, Thread also implements Runnable, which specifies only that there be a run( ) method. When you start the program, you create an object that's Runnable, but you don't start the thread. This must be done explicitly. So to produce a thread from a Runnable object, you must create a thread separately and hand it the Runnable object; there's a special constructor for this that takes a Runnable as its argument. You can then call start( ) for that thread: selfThread.start(); This performs the usual initialization and then calls run( ). The convenient aspect about the Runnable interface is that everything belongs to the same class. If you need to access something, you simply do it without going through a separate object. The penalty for this convenience is strict, though - you can have only a single thread running for that particular object (although you can create more objects of that type, or create other threads in different classes). Note that the Runnable interface is not what imposes this restriction. It's the combination of Runnable and your main class that does it, since you can have only one object of your main class per application. Making many threads Consider the creation of many different threads. You can't do this with the previous example, so you must go back to having separate classes inherited from Thread to encapsulate the run( ). But this is a more general solution and easier to understand, so while the previous example shows a coding style you'll often see, I can't recommend it for most cases because it's just a little bit more confusing and less flexible. The following example repeats the form of the examples above with counters and toggle buttons. But now all the information for a particular counter, including the button and text field, is inside its own object that is inherited from Thread. All the fields in Ticker are private, which means that the Ticker implementation can be changed at will, including the quantity and type of data components to acquire and display information. When a Ticker object is created, the constructor requires a handle to an AWT Container, which Ticker fills with its visual components. This way, if you change the visual components, the code that uses Ticker doesn't need to be modified. You can create as many threads as you want without explicitly creating the windowing components. In Counter4 there's an array of Ticker objects called s. For maximum flexibility, the size of this array is initialized by reaching out into the Web page using applet parameters. You'll notice that the determination of the size of the array s is done inside init( ), and not as part of an inline definition of s. It works fine if you move the getParameter( ) initialization inside of init( ). The applet framework performs the necessary startup to grab the parameters before entering init( ). In addition, this code is set up to be either an applet or an application. When it's an application the size argument is extracted from the command line (or a default value is provided). Once the size of the array is established, new Ticker objects are created; as part of the Ticker constructor the button and text field for each Ticker is added to the applet. Pressing the start button means looping through the entire array of Tickers and calling start( ) for each one. Remember, start( ) performs necessary thread initialization and then calls run( ) for that thread. The ToggleL listener simply inverts the flag in Ticker and when the associated thread next takes note it can react accordingly. One value of this example is that it allows you to easily create large sets of independent subtasks and to monitor their behavior. In this case, you'll see that as the number of subtasks gets larger, your machine will probably show more divergence in the displayed numbers because of the way that the threads are served. You can also experiment to discover how important the sleep(100) is inside Ticker.run( ). If you remove the sleep( ), things will work fine until you press a toggle button. Then that particular thread has a false runFlag and the run( ) is just tied up in a tight infinite loop, which appears difficult to break during multithreading, so the responsiveness and speed of the program really bogs down. Daemon threads A daemon thread is one that is supposed to provide a general service in the background as long as the program is running, but is not part of the essence of the program. Thus, when all of the non-daemon threads complete the program is terminated. Conversely, if there are any non-daemon threads still running the program doesn't terminate. If a thread is a daemon, then any threads it creates will automatically be daemons. Then it goes into an infinite loop that calls yield( ) to give up control to the other processes. In an earlier version of this program, the infinite loops would increment int counters, but this seemed to bring the whole program to a stop. Using yield( ) makes the program quite peppy. There's nothing to keep the program from terminating once main( ) finishes its job since there are nothing but daemon threads running. So that you can see the results of starting all the daemon threads, System.in is set up to read so the program waits for a carriage return before terminating. Without this you see only some of the results from the creation of the daemon threads. With multithreading, things aren't lonely anymore, but you now have the possibility of two or more threads trying to use the same limited resource at once. Colliding over a resource must be prevented or else you'll have two threads trying to access the same bank account at the same time, print to the same printer, or adjust the same valve, etc. Improperly accessing resources Consider a variation on the counters that have been used so far in this chapter. In the following example, each thread contains two counters that are incremented and displayed inside run( ). In addition, there's another thread of class Watcher that is watching the counters to see if they're always equivalent. This seems like a needless activity, since looking at the code it appears obvious that the counters will always be the same. But that's where the surprise comes in. These components are added to the Container in the TwoCounter constructor. Because this thread is started via a button press by the user, it's possible that start( ) could be called more than once. It's illegal for Thread.start( ) to be called more than once for a thread (an exception is thrown). You can see that the machinery to prevent this in the started flag and the overridden start( ) method. In run( ), count1 and count2 are incremented and displayed in a manner that would seem to keep them identical. Then sleep( ) is called; without this call the program balks because it becomes hard for the CPU to swap tasks. The synchTest( ) method performs the apparently useless activity of checking to see if count1 is equivalent to count2; if they are not equivalent it sets the label to Unsynched to indicate this. But first, it calls a static member of the class Sharing1 that increments and displays an access counter to show how many times this check has occurred successfully. It does this by stepping through the array that's kept in the Sharing1 object. You can think of the Watcher as constantly peeking over the shoulders of the TwoCounter objects. Sharing1 contains an array of TwoCounter objects that it initializes in init( ) and starts as threads when you press the start button. Later, when you press the Observe button, one or more observers are created and freed upon the unsuspecting TwoCounter threads. By changing the size and observers you'll change the behavior of the program. You can also see that this program is set up to run as a stand-alone application by pulling the arguments from the command line (or providing defaults). Here's the surprising part. In TwoCounter.run( ), the infinite loop is just repeatedly passing over the adjacent lines: t1.setText(Integer.toString(count1++)); t2.setText(Integer.toString(count2++)); (as well as sleeping, but that's not important here). When you run the program, however, you'll discover that count1 and count2 will be observed (by the Watcher) to be unequal at times! This is because of the nature of threads - they can be suspended at any time. So at times, the suspension occurs between the execution of the above two lines, and the Watcher thread happens to come along and perform the comparison at just this moment, thus finding the two counters to be different. This example shows a fundamental problem with using threads. You never know when a thread might be run. Imagine sitting at a table with a fork, about to spear the last piece of food on your plate and as your fork reaches for it, the food suddenly vanishes (because your thread was suspended and another thread came in and stole the food). That's the problem that you're dealing with. Sometimes you don't care if a resource is being accessed at the same time you're trying to use it (the food is on some other plate). But for multithreading to work, you need some way to prevent two threads from accessing the same resource, at least during critical periods. Preventing this kind of collision is simply a matter of putting a lock on a resource when one thread is using it. The first thread that accesses a resource locks it, and then the other threads cannot access that resource until it is unlocked, at which time another thread locks and uses it, etc. If the front seat of the car is the limited resource, the child who shouts Dibs! asserts the lock. How Java shares resources Java has built-in support to prevent collisions over one kind of resource: the memory in an object. Since you typically make the data elements of a class private and access that memory only through methods, you can prevent collisions by making a particular method synchronized. Only one thread at a time can call a synchronized method for a particular object (although that thread can call more than one of the object's synchronized methods). When you call any synchronized method, that object is locked and no other synchronized method of that object can be called until the first one finishes and releases the lock. In the example above, if f( ) is called for an object, g( ) cannot be called for the same object until f( ) is completed and releases the lock. Thus, there's a single lock that's shared by all the synchronized methods of a particular object, and this lock prevents common memory from being written by more than one method at a time (i.e. more than one thread at a time). There's also a single lock per class (as part of the Class object for the class), so that synchronized static methods can lock each other out from static data on a class-wide basis. Note that if you want to guard some other resource from simultaneous access by multiple threads, you can do so by forcing access to that resource through synchronized methods. Synchronizing the counters Armed with this new keyword it appears that the solution is at hand: we'll simply use the synchronized keyword for the methods in TwoCounter. If you synchronize only one of the methods, then the other is free to ignore the object lock and can be called with impunity. This is an important point: Every method that accesses a critical shared resource must be synchronized or it won't work right. Now a new issue arises. The Watcher2 can never get a peek at what's going on because the entire run( ) method has been synchronized, and since run( ) is always running for each object the lock is always tied up and synchTest( ) can never be called. You can see this because the accessCount never changes. What we'd like for this example is a way to isolate only part of the code inside run( ). The section of code you want to isolate this way is called a critical section and you use the synchronized keyword in a different way to set up a critical section. If some other thread already has this lock, then the block cannot be entered until the lock is given up. The Sharing2 example can be modified by removing the synchronized keyword from the entire run( ) method and instead putting a synchronized block around the two critical lines. But what object should be used as the lock? The one that is already respected by synchTest( ), which is the current object (this)! Of course, all synchronization depends on programmer diligence: every piece of code that can access a shared resource must be wrapped in an appropriate synchronized block. So if you know that a particular method will not cause contention problems it is expedient to leave off the synchronized keyword. Java Beans revisited Now that you understand synchronization you can take another look at Java Beans. Whenever you create a Bean, you must assume that it will run in a multithreaded environment. This means that: 1. Whenever possible, all the public methods of a Bean should be synchronized. Of course, this incurs the synchronized runtime overhead. If that's a problem, methods that will not cause problems in critical sections can be left un-synchronized, but keep in mind that this is not always obvious. Making such methods un-synchronized might not have a significant effect on the execution speed of your program. You might as well make all public methods of a Bean synchronized and remove the synchronized keyword only when you know for sure that it's necessary and that it makes a difference. 2. When firing a multicast event to a bunch of listeners interested in that event, you must assume that listeners might be added or removed while moving through the list. The first point is fairly easy to deal with, but the second point requires a little more thought. Consider the BangBean.java example presented in the last chapter. That ducked out of the multithreading question by ignoring the synchronized keyword (which hadn't been introduced yet) and making the event unicast. However, notice in addActionListener( ) and removeActionListener( ) that the ActionListeners are now added to and removed from a Vector, so you can have as many as you want. You can see that the method notifyListeners( ) is not synchronized. It can be called from more than one thread at a time. It's also possible for addActionListener( ) or removeActionListener( ) to be called in the middle of a call to notifyListeners( ), which is a problem since it traverses the Vector actionListeners. To alleviate the problem, the Vector is cloned inside a synchronized clause and the clone is traversed. This way the original Vector can be manipulated without impact on notifyListeners( ). The paint( ) method is also not synchronized. Deciding whether to synchronize overridden methods is not as clear as when you're just adding your own methods. In this example it turns out that paint( ) seems to work OK whether it's synchronized or not. But the issues you must consider are: 1. Does the method modify the state of critical variables within the object? To discover whether the variables are critical you must determine whether they will be read or set by other threads in the program. 2. Does the method depend on the state of these critical variables? If a synchronized method modifies a variable that your method uses, then you might very well want to make your method synchronized as well. Based on this, you might observe that cSize is changed by synchronized methods and therefore paint( ) should be synchronized. 3. A third clue is to notice whether the base-class version of paint( ) is synchronized, which it isn't. This isn't an airtight argument, just a clue. In this case, for example, a field that is changed via synchronized methods (that is cSize) has been mixed into the paint( ) formula and might have changed the situation. Notice, however, that synchronized doesn't inherit - that is, if a method is synchronized in the base class then it is not automatically synchronized in the derived class overridden version. The test code in TestBangBean2 has been modified from that in the previous chapter to demonstrate the multicast ability of BangBean2 by adding extra listeners. Blocking A thread can be in any one of four states: 1. New: the thread object has been created but it hasn't been started yet so it cannot run. 2. Runnable: This means that a thread can be run when the time-slicing mechanism has CPU cycles available for the thread. Thus, the thread might or might not be running, but there's nothing to prevent it from being run if the scheduler can arrange it; it's not dead or blocked. 3. Dead: the normal way for a thread to die is by returning from its run( ) method. You can also call stop( ), but this throws an exception that's a subclass of Error (which means you usually don't catch it). Remember that throwing an exception should be a special event and not part of normal program execution; thus the use of stop( ) is discouraged (and it's deprecated in Java 1.2). There's also a destroy( ) method (which has never been implemented) that you should never call if you can avoid it since it's drastic and doesn't release object locks. 4. Blocked: the thread could be run but there's something that prevents it. While a thread is in the blocked state the scheduler will simply skip over it and not give it any CPU time. Until a thread re-enters the runnable state it won't perform any operations. Becoming blocked The blocked state is the most interesting and is worth further examination. A thread can become blocked for five reasons: 1. You've put the thread to sleep by calling sleep(milliseconds), in which case it will not be run for the specified time. 2. You've suspended the execution of the thread with suspend( ). It will not become runnable again until the thread gets the resume( ) message. 3. You've suspended the execution of the thread with wait( ). It will not become runnable again until the thread gets the notify( ) or notifyAll( ) message. 5. The thread is trying to call a synchronized method on another object and that object's lock is not available. You can also call yield( ) (a method of the Thread class) to voluntarily give up the CPU so that other threads can run. However, the same thing happens if the scheduler decides that your thread has had enough time and jumps to another thread. That is, nothing prevents the scheduler from re-starting your thread. When a thread is blocked, there's some reason that it cannot continue running. The following example shows all five ways of becoming blocked. It all exists in a single file called Blocking.java, but it will be examined here in discrete pieces. A Blockable object contains a TextField called state that is used to display information about the object. The method that displays this information is update( ). The indicator of change in Blockable is an int i, which will be incremented by the run( ) method of the derived class. There's a thread of class Peeker that is started for each Blockable object, and the Peeker's job is to watch its associated Blockable object to see changes in i by calling read( ) and reporting them in its status TextField. This is important: Note that read( ) and update( ) are both synchronized, which means they require that the object lock be free. You'll see that the Peeker associated with this object will run along merrily until you start the thread, and then the Peeker stops cold. This is one form of blocking: since Sleeper1.run( ) is synchronized, and once the thread starts it's always inside run( ), the method never gives up the object lock and the Peeker is blocked. Sleeper2 provides a solution by making run un-synchronized. Only the change( ) method is synchronized, which means that while run( ) is in sleep( ), the Peeker can access the synchronized method it needs, namely read( ). Here you'll see that the Peeker continues running when you start the Sleeper2 thread. Suspending and resuming The next part of the example introduces the concept of suspension. The Thread class has a method suspend( ) to temporarily halt the thread and resume( ) that re-starts it at the point it was halted. Presumably, resume( ) is called by some thread outside the suspended one, and in this case there's a separate class called Resumer that does just that. Again, when you start this thread you'll see that its associated Peeker gets blocked waiting for the lock to become available, which never happens. This is fixed as before in SuspendResume2, which does not synchronize the entire run( ) method but instead uses a separate synchronized change( ) method. You should be aware that Java 1.2 deprecates the use of suspend( ) and resume( ), because suspend( ) holds the object's lock and is thus deadlock-prone. That is, you can easily get a number of locked objects waiting on each other, and this will cause your program to freeze. Although you might see them used in older programs you should not use suspend( ) and resume( ). The proper solution is described later in this chapter. Wait and notify The point with the first two examples is that both sleep( ) and suspend( ) do not release the lock as they are called. You must be aware of this when working with locks. On the other hand, the method wait( ) does release the lock when it is called, which means that other synchronized methods in the thread object could be called during a wait( ). In the following two classes, you'll see that the run( ) method is fully synchronized in both cases, however, the Peeker still has full access to the synchronized methods during a wait( ). This is because wait( ) releases the lock on the object as it suspends the method it's called within. You'll also see that there are two forms of wait( ). The first takes an argument in milliseconds that has the same meaning as in sleep( ): pause for this period of time. The difference is that in wait( ), the object lock is released and you can come out of the wait( ) because of a notify( ) as well as having the clock run out. The second form takes no arguments, and means that the wait( ) will continue until a notify( ) comes along and will not automatically terminate after a time. One fairly unique aspect of wait( ) and notify( ) is that both methods are part of the base class Object and not part of Thread as are sleep( ), suspend( ), and resume( ). Although this seems a bit strange at first - to have something that's exclusively for threading as part of the universal base class - it's essential because they manipulate the lock that's also part of every object. As a result, you can put a wait( ) inside any synchronized method, regardless of whether there's any threading going on inside that particular class. In fact, the only place you can call wait( ) is within a synchronized method or block. You can call wait( ) or notify( ) only for your own lock. Again, you can compile code that tries to use the wrong lock, but it will produce the same IllegalMonitorStateException message as before. You can't fool with someone else's lock, but you can ask another object to perform an operation that manipulates its own lock. So one approach is to create a synchronized method that calls notify( ) for its own object. This method, which is not part of WaitNotify2, acquires the lock on the wn2 object, at which point it's legal for it to call notify( ) for wn2 and you won't get the IllegalMonitorStateException. So wait( ) allows you to put the thread to sleep while waiting for the world to change, and only when a notify( ) or notifyAll( ) occurs does the thread wake up and check for changes. Thus, it provides a way to synchronize between threads. Blocking on IO If a stream is waiting for some IO activity, it will automatically block. The Sender puts data into the Writer and sleeps for a random amount of time. However, Receiver has no sleep( ), suspend( ), or wait( ). But when it does a read( ) it automatically blocks when there is no more data. Testing The main applet class is surprisingly simple because most of the work has been put into the Blockable framework. Basically, an array of Blockable objects is created, and since each one is a thread, they perform their own activities when you press the start button. There's also a button and actionPerformed( ) clause to stop all of the Peeker objects, which provides a demonstration of the alternative to the deprecated (in Java 1.2) stop( ) method of Thread. To set up a connection between the Sender and Receiver objects, a PipedWriter and PipedReader are created. Note that the PipedReader in must be connected to the PipedWriter out via a constructor argument. After that, anything that's placed in out can later be extracted from in, as if it passed through a pipe (hence the name). The in and out objects are then passed to the Receiver and Sender constructors, respectively, which treat them as Reader and Writer objects of any type (that is, they are upcast). The array of Blockable handles b is not initialized at its point of definition because the piped streams cannot be set up before that definition takes place (the need for the try block prevents this). When the Blockable threads are initially created, each one automatically creates and starts its own Peeker. So you'll see the Peekers running before the Blockable threads are started. This is essential, as some of the Peekers will get blocked and stop when the Blockable threads start, and it's essential to see this to understand that particular aspect of blocking. Thus, there's a continuous loop of threads waiting on each other and no one can move. This is called deadlock. The claim is that it doesn't happen that often, but when it happens to you it's frustrating to debug. There is no language support to help prevent deadlock; it's up to you to avoid it by careful design. These are not comforting words to the person who's trying to debug a deadlocking program. The reason that the stop( ) method is deprecated is because it is unsafe. It releases all the locks that the thread had acquired, and if the objects are in an inconsistent state (damaged) other threads can view and modify them in that state. The resulting problems can be subtle and difficult to detect. Instead of using stop( ), you should follow the example in Blocking.java and use a flag to tell the thread when to terminate itself by exiting its run( ) method. There are times when a thread blocks, such as when it is waiting for input, and it cannot poll a flag as it does in Blocking.java. When you press the button, the blocked handle is set to null so the garbage collector will clean it up, and then the object's interrupt( ) method is called. The first time you press the button you'll see that the thread quits, but after that there's no thread to kill so you just see that the button has been pressed. The suspend( ) and resume( ) methods turn out to be inherently deadlock-prone. When you call suspend( ), the target thread stops but it still holds any locks that it has acquired up to that point. So no other thread can access the locked resources until the thread is resumed. Any thread that wants to resume the target thread and also tries to use any of the locked resources produces deadlock. You should not use suspend( ) and resume( ), but instead put a flag in your Thread class to indicate whether the thread should be active or suspended. If the flag indicates that the thread is suspended, the thread goes into a wait using wait( ). When the flag indicates that the thread should be resumed the thread is restarted with notify( ). An example can be produced by modifying Counter2.java. To suspend, the flag is set to true by calling fauxSuspend( ) and this is detected inside run( ). The wait( ), as described earlier in this chapter, must be synchronized so that it has the object lock. If you follow the style shown in this program you can avoid using wait( ) and notify( ). The destroy( ) method of Thread has never been implemented; it's like a suspend( ) that cannot resume, so it has the same deadlock issues as suspend( ). However, this is not a deprecated method and it might be implemented in a future version of Java (after 1.2) for special situations in which the risk of a deadlock is acceptable. You might wonder why these methods, now deprecated, were included in Java in the first place. It seems a clear admission of a rather significant mistake to simply remove them outright (and pokes yet another hole in the arguments for Java's exceptional design and infallibility touted by Sun marketing people). The heartening part about the change is that it clearly indicates that the technical people and not the marketing people are running the show - they discovered a problem and they are fixing it. I find this much more promising and hopeful than leaving the problem in because fixing it would admit an error. It means that Java will continue to improve, even if it means a little discomfort on the part of Java programmers. I'd rather deal with the discomfort than watch the language stagnate. Priorities The priority of a thread tells the scheduler how important this thread is. If there are a number of threads blocked and waiting to be run, the scheduler will run the one with the highest priority first. However, this doesn't mean that threads with lower priority don't get run (that is, you can't get deadlocked because of priorities). Lower priority threads just tend to run less often. You can read the priority of a thread with getPriority( ) and change it with setPriority( ). The form of the prior counter examples can be used to show the effect of changing the priorities. Also notice the use of yield( ), which voluntarily hands control back to the scheduler. Without this the multithreading mechanism still works, but you'll notice it runs slowly (try removing the call to yield( )!). You could also call sleep( ), but then the rate of counting would be controlled by the sleep( ) duration instead of the priority. The init( ) in Counter5 creates an array of 10 Ticker2s; their buttons and fields are placed on the form by the Ticker2 constructor. Counter5 adds buttons to start everything up as well as increment and decrement the maximum priority of the threadgroup. In addition, there are labels that display the maximum and minimum priorities possible for a thread and a TextField to show the thread group's maximum priority. When you press an up or down button, that Ticker2's priority is fetched and incremented or decremented accordingly. When you run this program, you'll notice several things. First of all, the thread group's default priority is 5. Even if you decrement the maximum priority below 5 before starting the threads (or before creating the threads, which requires a code change), each thread will have a default priority of 5. The simple test is to take one counter and decrement its priority to one, and observe that it counts much slower. But now try to increment it again. You can get it back up to the thread group's priority, but no higher. Now decrement the thread group's priority a couple of times. The thread priorities are unchanged, but if you try to modify them either up or down you'll see that they'll automatically pop to the priority of the thread group. Also, new threads will still be given a default priority, even if that's higher than the group priority. It can't be done. You can only reduce thread group maximum priorities, not increase them. Thread groups All threads belong to a thread group. This can be either the default thread group or a group you explicitly specify when you create the thread. At creation, the thread is bound to a group and cannot change to a different group. Each application has at least one thread that belongs to the system thread group. If you create more threads without specifying a group, they will also belong to the system thread group. Thread groups must also belong to other thread groups. The thread group that a new one belongs to must be specified in the constructor. If you create a thread group without specifying a thread group for it to belong to, it will be placed under the system thread group. Thus, all thread groups in your application will ultimately have the system thread group as the parent. The reason for the existence of thread groups is hard to determine from the literature, which tends to be confusing on this subject. It's often cited as security reasons. According to Arnold and Gosling,1 Threads within a thread group can modify the other threads in the group, including any farther down the hierarchy. A thread cannot modify threads outside of its own group or contained groups. It's hard to know what modify is supposed to mean here. The following example shows a thread in a leaf subgroup modifying the priorities of all the threads in its tree of thread groups as well as calling a method for all the threads in its tree. Note that initialization happens in textual order so this code is legal. Two threads are created and placed in different thread groups. TestThread1 doesn't have a run( ) method but it does have an f( ) that modifies the thread and prints something so you can see it was called. TestThread2 is a subclass of TestThread1 and its run( ) is fairly elaborate. It first gets the thread group of the current thread, then moves up the heritage tree by two levels using getParent( ). The enumerate( ) method places handles to all of these threads in the array gAll, then I simply move through the entire array calling the f( ) method for each thread, as well as modifying the priority. Thus, a thread in a leaf thread group modifies threads in parent thread groups. The debugging method list( ) prints all the information about a thread group to standard output and is helpful when investigating thread group behavior. For threads, the thread name is printed, followed by the thread priority and the group that it belongs to. Note that list( ) indents the threads and thread groups to indicate that they are children of the un-indented thread group. You can see that f( ) is called by the TestThread2 run( ) method, so it's obvious that all threads in a group are vulnerable. However, you can access only the threads that branch off from your own system thread group tree, and perhaps this is what is meant by safety. You cannot access anyone else's system thread group tree. Controlling thread groups Putting aside the safety issue, one thing thread groups do seem to be useful for is control: you can perform certain operations on an entire thread group with a single command. The following example demonstrates this and the restrictions on priorities within thread groups. The commented numbers in parentheses provide a reference to compare to the output. From this thread, the thread group is produced and list( ) is called for the result. A new thread A is placed in g1. The fourth exercise reduces g1's maximum priority by two and then tries to increase it up to Thread.MAX_PRIORITY. You can only decrease a thread group's maximum priority, not increase it. Also, notice that thread A's priority didn't change, and now it is higher than the thread group's maximum priority. Changing a thread group's maximum priority doesn't affect existing threads. The default thread priority for this program is 6; that's the priority a new thread will be created at and where it will stay if you don't manipulate the priority. Thus, maximum thread group priority does not affect default priority. A similar experiment is performed in (8) and (9), in which a new thread group g2 is created as a child of g1 and its maximum priority is changed. The last part of this program demonstrates methods for an entire group of threads. First the program moves through the entire tree of threads and starts each one that hasn't been started. For drama, the system group is then suspended and finally stopped. Actually, if you do stop the main thread it throws a ThreadDeath exception, so this is not a typical thing to do. Since ThreadGroup is inherited from Object, which contains the wait( ) method, you can also choose to suspend the program for any number of seconds by calling wait(seconds * 1000). This must acquire the lock inside a synchronized block, of course. The ThreadGroup class also has suspend( ) and resume( ) methods so you can stop and start an entire thread group and all of its threads and subgroups with a single command. Runnable revisited Earlier in this chapter, I suggested that you think carefully before making an applet or main Frame as an implementation of Runnable. If you take that approach, you can make only one of those threads in your program. This limits your flexibility if you decide that you want to have more than one thread of that type. Of course, if you must inherit from a class and you want to add threading behavior to the class, Runnable is the correct solution. The final example in this chapter exploits this by making a Runnable Canvas class that paints different colors on itself. This application is set up to take values from the command line to determine how big the grid of colors is and how long to sleep( ) between color changes. This constructor takes an argument of int grid to set up the GridLayout so that it has grid cells in each dimension. Then it adds the appropriate number of CBox objects to fill the grid, passing the pause value to each one. In main( ) you can see how pause and grid have default values that can be changed if you pass in command-line arguments. CBox is where all the work takes place. This is inherited from Canvas and it implements the Runnable interface so each Canvas can also be a Thread. Remember that when you implement Runnable, you don't make a Thread object, just a class that has a run( ) method. Thus, you must explicitly create a Thread object and hand the Runnable object to the constructor, then call start( ) (this happens in the constructor). In CBox this thread is called t. Notice the array colors, which is an enumeration of all the colors in class Color. This is used in newColor( ) to produce a randomly-selected color. The current cell color is cColor. In run( ), you see the infinite loop that sets the cColor to a new random color and then calls repaint( ) to show it. Then the thread goes to sleep( ) for the amount of time specified on the command line. Precisely because this design is flexible and threading is tied to each Canvas element, you can experiment by making as many threads as you want. Too many threads At some point, you'll find that ColorBoxes bogs down. On my machine, this occurred somewhere after a 10 x 10 grid. Why does this happen? You're naturally suspicious that the AWT might have something to do with it, so here's an example that tests that premise by making fewer threads. The code is reorganized so that a Vector implements Runnable and that Vector holds a number of color blocks and randomly chooses ones to update. Then a number of these Vector objects are created, depending roughly on the grid dimension you choose. An equal number of Cbox2 objects is then added to each CBoxVector, and each vector is told to go( ), which starts its thread. CBox2 is similar to CBox: it paints itself with a randomly-chosen color. But that's all a CBox2 does. All of the threading has been moved into CBoxVector. The CBoxVector could also have inherited Thread and had a member object of type Vector. That design has the advantage that the addElement( ) and elementAt( ) methods could then be given specific argument and return value types instead of generic Objects. In addition, it automatically retains all the other behaviors of a Vector. With all the casting and parentheses necessary for elementAt( ), this might not be the case as your body of code grows. The run( ) method simply chooses a random element number within the vector and calls nextColor( ) for that element to cause it to choose a new randomly-selected color. Upon running this program, you see that it does indeed run faster and respond more quickly (for instance, when you interrupt it, it stops more quickly), and it doesn't seem to bog down as much at higher grid sizes. Thus, a new factor is added into the threading equation: you must watch to see that you don't have too many threads (whatever that turns out to mean for your particular program and platform). If you do, you must try to use techniques like the one above to balance the number of threads in your program. 2. Are calls to sleep( ) long enough? 3. Are you running too many threads? 4. Have you tried different platforms and JVMs? Issues like this are one reason that multithreaded programming is often considered an art. Summary It is vital to learn when to use multithreading and when to avoid it. The main reason to use it is to manage a number of tasks whose intermingling will make more efficient use of the computer or be more convenient for the user. The classic example of user convenience is monitoring a stop button during long downloads. Since all threads in a given process share the same memory space, a light context switch changes only program execution and local variables. On the other hand, a process change, the heavy context switch, must exchange the full memory space. Threading is like stepping into an entirely new world and learning a whole new programming language, or at least a new set of language concepts. With the appearance of thread support in most microcomputer operating systems, extensions for threads have also been appearing in programming languages or libraries. In all cases, thread programming (1) seems mysterious and requires a shift in the way you think about programming and (2) looks similar to thread support in other languages, so when you understand threads, you understand a common tongue. And although support for threads can make Java seem like a more complicated language, don't blame Java. Threads are tricky. This requires judicious use of the synchronized keyword, which is a helpful tool but must be understood thoroughly because it can quietly introduce deadlock situations. In addition, there's a certain art to the application of threads. Java is designed to allow you to create as many objects as you need to solve your problem - at least in theory. This critical point is not in the many thousands as it might be with objects, but rather in the neighborhood of less than 100. As you often create only a handful of threads to solve a problem, this is typically not much of a limit, yet in a more general design it becomes a constraint. A significant non-intuitive issue in threading is that, because of thread scheduling, you can typically make your applications run faster by inserting calls to sleep( ) inside run( )'s main loop. This definitely makes it feel like an art, in particular when the longer delays seem to speed up performance. It takes extra thought to realize how messy things can get. One thing you might notice missing in this chapter is an animation example, which is one of the most popular things to do with applets. However, a complete solution (with sound) to this problem comes with the Java JDK (available at java.sun.com) in the demo section. For explanations about how Java animation works, see Core Java by Cornell and Horstmann, Prentice-Hall 1997. For more advanced discussions of threading, see Concurrent Programming in Java by Doug Lea, Addison-Wesley 1997, or Java Threads by Oaks and Wong, O'Reilly 1997. Exercises 1. Inherit a class from Thread and override the run( ) method. Inside run( ), print a message, then call sleep( ). Repeat this three times, then return from run( ). Put a start-up message in the constructor and override finalize( ) to print a shut-down message. Make a separate thread class that calls System.gc( ) and System.runFinalization( ) inside run( ), printing a message as it does so. Make several thread objects of both types and run them to see what happens. 2. Modify Counter2.java so that the thread is an inner class and doesn't need to explicitly store a handle to a Counter2. 3. Modify Sharing2.java to add a synchronized block inside the run( ) method of TwoCounter instead of synchronizing the entire run( ) method. 4. Create two Thread subclasses, one with a run( ) that starts up, captures the handle of the second Thread object and then calls wait( ). The other class' run( ) should call notifyAll( ) for the first thread after some number of seconds have passed, so the first thread can print out a message. 5. In Counter5.java inside Ticker2, remove the yield( ) and explain the results. Replace the yield( ) with a sleep( ) and explain the results. 6. In ThreadGroup1.java, replace the call to sys.suspend( ) with a call to wait( ) for the thread group, causing it to wait for two seconds. For this to work correctly you must acquire the lock for sys inside a synchronized block. 7. Change Daemons.java so that main( ) has a sleep( ) instead of a readLine( ). Experiment with different sleep times to see what happens. 8. (Intermediate) In Chapter 7, locate the GreenhouseControls.java example, which consists of three files. In Event.java, the class Event is based on watching the time. Change Event so that it is a Thread, and change the rest of the design so that it works with this new Thread-based Event. The programmer had to know many details about the network and sometimes even the hardware. It was a daunting task. However, the concept of networking is not so difficult. You want to get some information from that machine over there and move it to this machine here, or vice versa. It's quite similar to reading and writing files, except that the file exists on a remote machine and the remote machine can decide exactly what it wants to do about the information you're requesting or sending. One of Java's great strengths is painless networking. As much as possible, the underlying details of networking have been abstracted away and taken care of within the JVM and local machine installation of Java. The programming model you use is that of a file; in fact, you actually wrap the network connection (a socket) with stream objects, so you end up using the same method calls as you do with all other streams. In addition, Java's built-in multithreading is exceptionally handy when dealing with another networking issue: handling multiple connections at once. This chapter introduces Java's networking support using easy-to-understand examples. Identifying a machine Of course, in order to tell one machine from another and to make sure that you are connected with the machine you want, there must be some way of uniquely identifying machines on a network. Early networks were satisfied to provide unique names for machines within the local network. However, Java works within the Internet, which requires a way to uniquely identify a machine from all the others in the world. This is accomplished with the IP (Internet Protocol) address that can exist in two forms: 1. The familiar DNS (Domain Name Service) form. My domain name is bruceeckel.com, so suppose I have a computer called Opus in my domain. Its domain name would be Opus.bruceeckel.com. This is exactly the kind of name that you use when you send email to people, and is often incorporated into a World-Wide-Web address. 2. Alternatively, you can use the dotted quad form, which is four numbers separated by dots, such as 123.255.28.120. The result is an object of type InetAddress that you can use to build a socket as you will see later. As a simple example of using InetAddress.getByName( ), consider what happens if you have a dial-up Internet service provider (ISP). Each time you dial up, you are assigned a temporary IP address. But while you're connected, your IP address has the same validity as any other IP address on the Internet. If someone connects to your machine using your IP address then they can connect to a Web server or FTP server that you have running on your machine. Of course, they need to know your IP address, and since it's assigned each time you dial up, how can you find out what it is? The following program uses InetAddress.getByName( ) to produce your IP address. To use it, you must know the name of your computer. It has been tested only on Windows 95, but there you can go to Settings, Control Panel, Network, and then select the Identification tab. Computer name is the name to put on the command line. This can sometimes be a handy way to distribute information to someone else or to test out a Web site configuration before posting it to a real server. Servers and clients The whole point of a network is to allow two machines to connect and talk to each other. Once the two machines have found each other they can have a nice, two-way conversation. But how do they find each other? This distinction is important only while the client is trying to connect to the server. Once they've connected, it becomes a two-way communication process and it doesn't matter anymore that one happened to take the role of server and the other happened to take the role of the client. So the job of the server is to listen for a connection, and that's performed by the special server object that you create. The job of the client is to try to make a connection to a server, and this is performed by the special client object you create. Once the connection is made, you'll see that at both server and client ends, the connection is just magically turned into an IO stream object, and from then on you can treat the connection as if you were reading from and writing to a file. Thus, after the connection is made you will just use the familiar IO commands from Chapter 10. This is one of the nice features of Java networking. Testing programs without a network For many reasons, you might not have a client machine, a server machine, and a network available to test your programs. You might be performing exercises in a classroom situation, or you could be writing programs that aren't yet stable enough to put onto the network. The creators of the Internet Protocol were aware of this issue, and they created a special address called localhost to be the local loopback IP address for testing without a network. The generic way to produce this address in Java is: InetAddress addr = InetAddress.getByName(null); If you hand getByName( ) a null, it defaults to using the localhost. The InetAddress is what you use to refer to the particular machine, and you must produce this before you can go any further. You can't manipulate the contents of an InetAddress (but you can print them out, as you'll see in the next example). The only way you can create an InetAddress is through one of that class's static member methods getByName( ) (which is what you'll usually use), getAllByName( ), or getLocalHost( ). Port: a unique place within the machine An IP address isn't enough to identify a unique server, since many servers can exist on one machine. The port is not a physical location in a machine, but a software abstraction (mainly for bookkeeping purposes). The client program knows how to connect to the machine via its IP address, but how does it connect to a desired service (potentially one of many on that machine)? That's where the port numbers come in as second level of addressing. The idea is that if you ask for a particular port, you're requesting the service that's associated with the port number. The time of day is a simple example of a service. Typically, each service is associated with a unique port number on a given server machine. It's up to the client to know ahead of time which port number the desired service is running on. The system services reserve the use of ports 1 through 1024, so you shouldn't use those or any other port that you know to be in use. Sockets The socket is the software abstraction used to represent the terminals of a connection between two machines. For a given connection, there's a socket on each machine, and you can imagine a hypothetical cable running between the two machines with each end of the cable plugged into a socket. Of course, the physical hardware and cabling between machines is completely unknown. The whole point of the abstraction is that we don't have to know more than is necessary. There are two stream-based socket classes: a ServerSocket that a server uses to listen for incoming connections and a Socket that a client uses in order to initiate a connection. Once a client makes a socket connection, the ServerSocket returns (via the accept( ) method) a corresponding server side Socket through which direct communications will take place. From then on, you have a true Socket to Socket connection and you treat both ends the same way because they are the same. At this point, you use the methods getInputStream( ) and getOutputStream( ) to produce the corresponding InputStream and OutputStream objects from each Socket. These must be wrapped inside buffers and formatting classes just like any other stream object described in Chapter 10. The use of the term ServerSocket would seem to be another example of a confusing name scheme in the Java libraries. You might think ServerSocket would be better named ServerConnector or something without the word Socket in it. You might also think that ServerSocket and Socket should both be inherited from some common base class. Indeed, the two classes do have several methods in common but not enough to give them a common base class. Instead, ServerSocket's job is to wait until some other machine connects to it, then to return an actual Socket. This is why ServerSocket seems to be a bit misnamed, since its job isn't really to be a socket but instead to make a Socket object when someone else connects to it. However, the ServerSocket does create a physical server or listening socket on the host machine. This socket listens for incoming connections and then returns an established socket (with the local and remote endpoints defined) via the accept( ) method. The confusing part is that both of these sockets (listening and established) are associated with the same server socket. The listening socket can accept only new connection requests and not data packets. So while ServerSocket doesn't make much sense programmatically, it does physically. When you create a ServerSocket, you give it only a port number. You don't have to give it an IP address because it's already on the machine it represents. When you create a Socket, however, you must give both the IP address and the port number where you're trying to connect. All the server does is wait for a connection, then uses the Socket produced by that connection to create an InputStream and OutputStream. After that, everything it reads from the InputStream it echoes to the OutputStream until it receives the line END, at which time it closes the connection. The client makes the connection to the server, then creates an OutputStream. Lines of text are sent through the OutputStream. The client also creates an InputStream to hear what the server is saying (which, in this case, is just the words echoed back). Both the server and client use the same port number and the client uses the local loopback address to connect to the server on the same machine so you don't have to test it over a network. When you call accept( ), the method blocks until some client tries to connect to it. That is, it's there waiting for a connection but other processes can run (see Chapter 14). When a connection is made, accept( ) returns with a Socket object representing that connection. The responsibility for cleaning up the sockets is crafted carefully here. If the ServerSocket constructor fails, the program just quits (notice we must assume that the constructor for ServerSocket doesn't leave any open network sockets lying around if it fails). For this case, main( ) throws IOException so a try block is not necessary. If the ServerSocket constructor is successful then all other method calls must be guarded in a try-finally block to ensure that, no matter how the block is left, the ServerSocket is properly closed. The same logic is used for the Socket returned by accept( ). If accept( ) fails, then we must assume that the Socket doesn't exist or hold any resources, so it doesn't need to be cleaned up. If it's successful, however, the following statements must be in a try-finally block so that if they fail the Socket will still be cleaned up. Care is required here because sockets use important non-memory resources, so you must be diligent in order to clean them up (since there is no destructor in Java to do it for you). Both the ServerSocket and the Socket produced by accept( ) are printed to System.out. This means that their toString( ) methods are automatically called. The next part of the program looks just like opening files for reading and writing except that the InputStream and OutputStream are created from the Socket object. Both the InputStream and OutputStream objects are converted to Java 1.1 Reader and Writer objects using the converter classes InputStreamReader and OutputStreamWriter, respectively. You could also have used the Java 1.0 InputStream and OutputStream classes directly, but with output there's a distinct advantage to using the Writer approach. This appears with PrintWriter, which has an overloaded constructor that takes a second argument, a boolean flag that indicates whether to automatically flush the output at the end of each println( ) (but not print( )) statement. Every time you write to out, its buffer must be flushed so the information goes out over the network. Flushing is important for this particular example because the client and server each wait for a line from the other party before proceeding. If flushing doesn't occur, the information will not be put onto the network until the buffer is full, which causes lots of problems in this example. When writing network programs you need to be careful about using automatic flushing. Every time you flush the buffer a packet must be created and sent. In this case, that's exactly what we want, since if the packet containing the line isn't sent then the handshaking back and forth between server and client will stop. Put another way, the end of a line is the end of a message. But in many cases messages aren't delimited by lines so it's much more efficient to not use auto flushing and instead let the built-in buffering decide when to build and send a packet. This way, larger packets can be sent and the process will be faster. Note that, like virtually all streams you open, these are buffered. There's an exercise at the end of the chapter to show you what happens if you don't buffer the streams (things get slow). The infinite while loop reads lines from the BufferedReader in and writes information to System.out and to the PrintWriter out. Note that these could be any streams, they just happen to be connected to the network. When the client sends the line consisting of END the program breaks out of the loop and closes the Socket. To understand what it means when you print out one of these Socket objects, remember that an Internet connection is determined uniquely by these four pieces of data: clientHost, clientPortNumber, serverHost, and serverPortNumber. When the server comes up, it takes up its assigned port (8080) on the localhost (127.0.0.1). When the client comes up, it is allocated to the next available port on its machine, 1077 in this case, which also happens to be on the same machine (127.0.0.1) as the server. Now, in order for data to move between the client and server, each side has to know where to send it. Therefore, during the process of connecting to the known server, the client sends a return address so the server knows where to send its data. You'll notice that every time you start up the client anew, the local port number is incremented. It starts at 1025 (one past the reserved block of ports) and keeps going up until you reboot the machine, at which point it starts at 1025 again. Here, the client initiates the conversation by sending the string howdy followed by a number. Note that the buffer must again be flushed (which happens automatically via the second argument to the PrintWriter constructor). If the buffer isn't flushed, the whole conversation will hang because the initial howdy will never get sent (the buffer isn't full enough to cause the send to happen automatically). Each line that is sent back from the server is written to System.out to verify that everything is working correctly. To terminate the conversation, the agreed-upon END is sent. If the client simply hangs up, then the server throws an exception. You can see that the same care is taken here to ensure that the network resources represented by the Socket are properly cleaned up, using a try-finally block. Sockets produce a dedicated connection that persists until it is explicitly disconnected. This seems like a logical approach to networking, but it puts an extra load on the network. Later in the chapter you'll see a different approach to networking, in which the connections are only temporary. Serving multiple clients The JabberServer works, but it can handle only one client at a time. In a typical server, you'll want to be able to deal with many clients at once. The answer is multithreading, and in languages that don't directly support multithreading this means all sorts of complications. In Chapter 14 you saw that multithreading in Java is about as simple as possible, considering that multithreading is a rather complex topic. Because threading in Java is reasonably straightforward, making a server that handles multiple clients is relatively easy. The basic scheme is to make a single ServerSocket in the server and call accept( ) to wait for a new connection. When accept( ) returns, you take the resulting Socket and use it to create a new thread whose job is to serve that particular client. Then you call accept( ) again to wait for a new client. Then, as before, it creates a BufferedReader and auto-flushed PrintWriter object using the Socket. Finally, it calls the special Thread method start( ), which performs thread initialization and then calls run( ). This performs the same kind of action as in the previous example: reading something from the socket and then echoing it back until it reads the special END signal. The responsibility for cleaning up the socket must again be carefully designed. In this case, the socket is created outside of the ServeOneJabber so the responsibility can be shared. If the ServeOneJabber constructor fails, it will just throw the exception to the caller, who will then clean up the thread. But if the constructor succeeds, then the ServeOneJabber object takes over responsibility for cleaning up the thread, in its run( ). Notice the simplicity of the MultiJabberServer. As before, a ServerSocket is created and accept( ) is called to allow a new connection. But this time, the return value of accept( ) (a Socket) is passed to the constructor for ServeOneJabber, which creates a new thread to handle that connection. When the connection is terminated, the thread simply goes away. If the creation of the ServerSocket fails, the exception is again thrown through main( ). But if it succeeds, the outer try-finally guarantees its cleanup. The inner try-catch guards only against the failure of the ServeOneJabber constructor; if the constructor succeeds, then the ServeOneJabber thread will close the associated socket. To test that the server really does handle multiple clients, the following program creates many clients (using threads) that connect to the same server. Each thread has a limited lifetime, and when it goes away, that leaves space for the creation of a new thread. The maximum number of threads allowed is determined by the final int maxthreads. You'll notice that this value is rather critical, since if you make it too high the threads seem to run out of resources and the program mysteriously fails. Here, messages are sent to the server and information from the server is echoed to the screen. However, the thread has a limited lifetime and eventually completes. Note that the socket is cleaned up if the constructor fails after the socket is created but before the constructor completes. Otherwise the responsibility for calling close( ) for the socket is relegated to the run( ) method. The threadcount keeps track of how many JabberClientThread objects currently exist. It is incremented as part of the constructor and decremented as run( ) exits (which means the thread is terminating). In MultiJabberClient.main( ), you can see that the number of threads is tested, and if there are too many, no more are created. Then the method sleeps. This way, some threads will eventually terminate and more can be created. You can experiment with MAX_THREADS to see where your particular system begins to have trouble with too many connections. Datagrams The examples you've seen so far use the Transmission Control Protocol (TCP, also known as stream-based sockets), which is designed for ultimate reliability and guarantees that the data will get there. It allows retransmission of lost data, it provides multiple paths through different routers in case one goes down, and bytes are delivered in the order they are sent. All this control and reliability comes at a cost: TCP has a high overhead. There's a second protocol, called User Datagram Protocol (UDP), which doesn't guarantee that the packets will be delivered and doesn't guarantee that they will arrive in the order they were sent. It's called an unreliable protocol (TCP is a reliable protocol), which sounds bad, but because it's much faster it can be useful. There are some applications, such as an audio signal, in which it isn't so critical if a few packets are dropped here or there but speed is vital. Or consider a time-of-day server, where it really doesn't matter if one of the messages is lost. Also, some applications might be able to fire off a UDP message to a server and can then assume, if there is no response in a reasonable period of time, that the message was lost. The support for datagrams in Java has the same feel as its support for TCP sockets, but there are significant differences. With datagrams, you put a DatagramSocket on both the client and server, but there is no analogy to the ServerSocket that waits around for a connection. That's because there is no connection, but instead a datagram just shows up. Another fundamental difference is that with TCP sockets, once you've made the connection you don't need to worry about who's talking to whom anymore; you just send the data back and forth through conventional streams. However, with datagrams, the datagram packet must know where it came from and where it's supposed to go. That means you must know these things for each datagram packet that you load up and ship off. A DatagramSocket sends and receives the packets, and the DatagramPacket contains the information. So the constructor for a DatagramPacket to receive datagrams is: DatagramPacket(buf, buf.length) in which buf is an array of byte. Since buf is an array, you might wonder why the constructor couldn't figure out the length of the array on its own. I wondered this, and can only guess that it's a throwback to C-style programming, in which of course arrays can't tell you how big they are. You can reuse a receiving datagram; you don't have to make a new one each time. Every time you reuse it, the data in the buffer is overwritten. The maximum size of the buffer is restricted only by the allowable datagram packet size, which limits it to slightly less than 64Kbytes. However, in many applications you'll want it to be much smaller, certainly when you're sending data. Your chosen packet size depends on what you need for your particular application. When you send a datagram, the DatagramPacket must contain not only the data, but also the Internet address and port where it will be sent. So the constructor for an outgoing DatagramPacket is: DatagramPacket(buf, length, inetAddress, port) This time, buf (which is a byte array) already contains the data that you want to send out. The length might be the length of buf, but it can also be shorter, indicating that you want to send only that many bytes. Good OO design would suggest that these should be two different classes, rather than one class with different behavior depending on how you construct the object. This is probably true, but fortunately the use of DatagramPackets is simple enough that you're not tripped up by the problem, as you can see in the following example. This example is similar to the MultiJabberServer and MultiJabberClient example for TCP sockets. Multiple clients will send datagrams to a server, which will echo them back to the same client that sent the message. Notice the +1 in the buffer allocation - this was necessary to prevent truncation. The getBytes( ) method of String is a special operation that copies the chars of a String into a byte buffer. This method is now deprecated; Java 1.1 has a better way to do this but it's commented out here because it truncates the String. So you'll get a deprecation message when you compile it under Java 1.1, but the behavior will be correct. The single DatagramSocket can be used repeatedly. This DatagramSocket has a port number because this is the server and the client must have an exact address where it wants to send the datagram. It is given a port number but not an Internet address because it resides on this machine so it knows what its Internet address is (in this case, the default localhost). In the infinite while loop, the socket is told to receive( ), whereupon it blocks until a datagram shows up, and then sticks it into our designated receiver, the DatagramPacket dp. The packet is converted to a String along with information about the Internet address and socket where the packet came from. This information is displayed, and then an extra string is added to indicate that it is being echoed back from the server. Now there's a bit of a quandary. As you will see, there are potentially many different Internet addresses and port numbers that the messages might come from - that is, the clients can reside on any machine. This is an essential part of datagrams: you don't need to keep track of where a message came from because it's always stored inside the datagram. In fact, the most reliable way to program is if you don't try to keep track, but instead always extract the address and port from the datagram in question (as is done here). To test this server, here's a program that makes a number of clients, all of which fire datagram packets to the server and wait for the server to echo them back. Here you can see that the receiving DatagramPacket looks just like the one used for ChatterServer. In the constructor, the DatagramSocket is created with no arguments since it doesn't need to advertise itself as being at a particular port number. The Internet address used for this socket will be this machine (for the example, localhost) and the port number will be automatically assigned, as you will see from the output. This DatagramSocket, like the one for the server, will be used both for sending and receiving. The hostAddress is the Internet address of the host machine you want to talk to. The one part of the program in which you must know an exact Internet address and port number is the part in which you make the outgoing DatagramPacket. As is always the case, the host must be at a known address and port number so that clients can originate conversations with the host. Each thread is given a unique identification number (although the port number automatically assigned to the thread would also provide a unique identifier). In run( ), a message String is created that contains the thread's identification number and the message number this thread is currently sending. This String is used to create a datagram that is sent to the host at its address; the port number is taken directly from a constant in ChatterServer. Once the message is sent, receive( ) blocks until the server replies with an echoing message. All of the information that's shipped around with the message allows you to see that what comes back to this particular thread is derived from the message that originated from it. In this example, even though UDP is an unreliable protocol, you'll see that all of the datagrams get where they're supposed to. You might think that the only right way to, for example, transfer a file from one machine to another is through TCP sockets, since they're reliable. However, because of the speed of datagrams they can actually be a better solution. You simply break the file up into packets and number each packet. The receiving machine takes the packets and reassembles them; a header packet tells the machine how many to expect and any other important information. If a packet is lost, the receiving machine sends a datagram back telling the sender to retransmit. A Web application Now let's consider creating an application to run on the Web, which will show Java in all its glory. Part of this application will be a Java program running on the Web server, and the other part will be an applet that's downloaded to the browser. The applet collects information from the user and sends it back to the application running on the Web server. The application running on the server will capture the data and check a data file in which all of the email addresses are kept. If that address is already in the file, it will send back a message to that effect, which is displayed by the applet. If the address isn't in the file, it is placed in the list and the applet is informed that the address was added successfully. Traditionally, the way to handle such a problem is to create an HTML page with a text field and a submit button. The user can type whatever he or she wants into the text field, and it will be submitted to the server without question. As it submits the data, the Web page also tells the server what to do with the data by mentioning the Common Gateway Interface (CGI) program that the server should run after receiving this data. This CGI program is typically written in either Perl or C (and sometimes C++, if the server supports it), and it must handle everything. First it looks at the data and decides whether it's in the correct format. If not, the CGI program must create an HTML page to describe the problem; this page is handed to the server, which sends it back to the user. The user must then back up a page and try again. If the data is correct, the CGI program opens the data file and either adds the email address to the file or discovers that the address is already in the file. In both cases it must format an appropriate HTML page for the server to return to the user. As Java programmers, this seems like an awkward way for us to solve the problem, and naturally, we'd like to do the whole thing in Java. First, we'll use a Java applet to take care of data validation at the client site, without all that tedious Web traffic and page formatting. Then let's skip the Perl CGI script in favor of a Java application running on the server. In fact, let's skip the Web server altogether and simply make our own network connection from the applet to the Java application on the server! As you'll see, there are a number of issues that make this a more complicated problem than it seems. It would be ideal to write the applet using Java 1.1 but that's hardly practical. At this writing, the number of users running Java 1.1-enabled browsers is small, and although such browsers are now commonly available, you'll probably need to take into account that a significant number of users will be slow to upgrade. So to be on the safe side, the applet will be programmed using only Java 1.0 code. With this in mind, there will be no JAR files to combine.class files in the applet, so the applet should be designed to create as few .class files as possible to minimize download time. Well, it turns out the Web server (the one available to me when I wrote the example) does have Java in it, but only Java 1.0! So the server application must also be written using Java 1.0. The server application Now consider the server application, which will be called NameCollector. What happens if more than one user at a time tries to submit their email addresses? But all of these threads will try to write to a single file where all the email addresses will be kept. This would require a locking mechanism to make sure that more than one thread doesn't access the file at once. A semaphore will do the trick, but perhaps there's a simpler way. If we use datagrams instead, multithreading is unnecessary. A single datagram socket will listen for incoming datagrams, and when one appears the program will process the message and send the reply as a datagram back to whomever sent the request. If the datagram gets lost, then the user will notice that no reply comes and can then re-submit the request. When the server application receives a datagram and unpacks it, it must extract the email address and check the file to see if that address is there already (and if it isn't, add it). And now we run into another problem. It turns out that Java 1.0 doesn't quite have the horsepower to easily manipulate the file containing the email addresses (Java 1.1 does). However, the problem can be solved in C quite readily, and this will provide an excuse to show you the easiest way to connect a non-Java program to a Java program. A Runtime object for a program has a method called exec( ) that will start up a separate program on the machine and return a Process object. You can get an OutputStream that connects to standard input for this separate program and an InputStream that connects to standard output. All you need to do is write a program using any language that takes its input from standard input and writes the output to standard output. This is a convenient trick when you run into a problem that can't be solved easily or quickly enough in Java (or when you have legacy code you don't want to rewrite). You can also use Java's native methods (see Appendix A) but those are much more involved. The C program The job of this non-Java application (written in C because Java wasn't appropriate for CGI programming; if nothing else, the startup time is prohibitive) is to manage the list of email addresses. Standard input will accept an email address and the program will look up the name in the list to see if it's already there. If not, it will add it and report success, but if the name is already there then it will report that. Don't worry if you don't completely understand what the following code means; it's just one example of how you can write a program in another language and use it from Java. The particular programming language doesn't really matter as long as it can read from standard input and write to standard output. The first function in the file checks to see whether the name you hand it as a second argument (a pointer to a char) is in the file. Here, the file is passed as a FILE pointer to an already-opened file (the file is opened inside main( )). The function fseek( ) moves around in the file; here it is used to move to the top of the file. This is inside a while loop so that each line in the file is read. Next, strchr( ) is used to locate the newline character so that it can be stripped off. Finally, strcmp( ) is used to compare the name you've passed into the function to the current line int the file. In this case the function exits and a one is returned to indicate that yes, the name was already in the list. In main( ), the file is opened using fopen( ). This is dealt with by printing an error message with perror( ) and terminating the program with exit( ). Assuming that the file was opened successfully, the program enters an infinite loop. The function call gets(buf) gets a line from standard input (which will be connected to the Java program, remember) and places it in the buffer buf. This is simply passed to the alreadyInList( ) function, and if it's already in the list, printf( ) sends that message to standard output (where the Java program is listening). If the name is not already in the list, fseek( ) is used to move to the end of the list and fprintf( ) prints the name to the end of the list. Then printf( ) is used to indicate that the name was added to the list (again flushing standard output) and the infinite loop goes back to waiting for a new name. Remember that you usually cannot compile this program on your computer and load it onto the Web server machine, since that machine might use a different processor and operating system. For example, my Web server runs on an Intel processor but it uses Linux, so I must download the source code and compile using remote commands (via telnet) with the C compiler that comes with the Linux distribution. The Java program This program will first start the C program above and make the necessary connections to talk to it. Then it will create a datagram socket that will be used to listen for datagram packets from the applet. These must of course be wrapped as is usual with Java IO, so we end up with a PrintStream and DataInputStream. All the work for this program happens inside the constructor. To start up the C program, the current Runtime object is procured. This is used to call exec( ), which returns the Process object. You can see that there are simple calls to produce the streams from the Process object: getOutputStream( ) and getInputStream( ). From this point on, all you need to consider is sending data to the stream nameList and getting the results from addResult. As before, a DatagramSocket is connected to a port. Inside the infinite while loop, the program calls receive( ), which blocks until a datagram shows up. When the datagram appears, its contents are extracted into the String rcvd. There are other ways to talk to non-Java code, which are discussed in Appendix A. Capturing the result from the C program is slightly more complicated. You must call read( ) and provide a buffer where the results will be placed. The return value for read( ) is the number of bytes that came from the C program, and if this value is -1 it means that something is wrong. Otherwise, the resultBuf is turned into a String and the spaces are trimmed off. This string is then placed into a DatagramPacket as before and shipped back to the same address that sent the request in the first place. Note that the sender's address is part of the DatagramPacket we received. Remember that although the C program must be compiled on the Web server, the Java program can be compiled anywhere since the resulting byte codes will be the same regardless of the platform on which the program will be running. The NameSender applet As mentioned earlier, the applet must be written with Java 1.0 so that it will run on the largest number of browsers, so it's best if the number of classes produced is minimized. Thus, instead of using the Dgram class developed earlier, all of the datagram manipulations will be placed in line. In addition, the applet needs a thread to listen for the reply from the server, and instead of making this a separate thread it's integrated into the applet by implementing the Runnable interface. There's a TextField in which you type your email address, and a Button to send the email address to the server. Two Labels are used to report status back to the user. By now you can recognize the DatagramSocket, InetAddress, buffer, and DatagramPacket as trappings of the network connection. Lastly, you can see the run( ) method that implements the thread portion so the applet can listen for the reply sent back by the server. The init( ) method sets up the GUI with the familiar layout tools, then creates the DatagramSocket that will be used both for sending and receiving datagrams. The action( ) method (remember, we're confined to Java 1.0 now, so we can't use any slick inner listener classes) watches only to see if you press the send button. When the button is pressed, the first action is to check the Thread pl to see if it's null. If it's not null, there's a live thread running. The first time the message is sent a thread is started up to watch for the reply. Thus, if a thread is running, it means this is not the first time the user has tried to send the message. The pl handle is set to null and the old listener is interrupted. The next group of statements checks the email name for errors. The String.indexOf( ) method is used to search for illegal characters, and if one is found it is reported to the user. Note that all of this happens without any network activity, so it's fast and it doesn't bog down the Internet. Once the name is verified, it is packaged into a datagram and sent to the host address and port number in the same way that was described in the earlier datagram example. The run( ) method for the thread uses the DatagramSocket that lives in NameSender to receive( ), which blocks until the datagram packet comes from the server. The resulting packet is placed into NameSender's DatagramPacket dp. The data is retrieved from the packet and placed into the second label in NameSender. At this point, the thread terminates and becomes dead. If the reply doesn't come back from the server in a reasonable amount of time, the user might become impatient and press the button again, thus terminating the current thread (and, after re-sending the data, starting a new one). Because a thread is used to listen for the reply, the user still has full use of the UI. The Web page Of course, the applet must go inside a Web page. Type in your email address and press the button to automatically add yourself to this mailing list. Problems with this approach This certainly seems like an elegant approach. There's no CGI programming and so there are no delays while the server starts up a CGI program. The datagram approach seems to produce a nice quick response. In addition, when Java 1.1 is available everywhere, the server portion can be written entirely in Java. One problem is rather subtle: since the Java application is running constantly on the server and it spends most of its time blocked in the Datagram.receive( ) method, there might be some CPU hogging going on. At least, that's the way it appeared on the server where I was experimenting. In any event, it's worth keeping your eye on an application like this - a blocked receive( ) could hog the CPU. The second problem is a show stopper. It concerns firewalls. A firewall is a machine that sits between your network and the Internet. It monitors all traffic coming in from the Internet and going out to the Internet, and makes sure that traffic conforms to what it expects. Firewalls are conservative little beasts. They demand strict conformance to all the rules, and if you're not conforming they assume that you're doing something sinful and shut you out (not quite so bad as the Spanish Inquisition, but close). For example, if you are on a network behind a firewall and you start connecting to the Internet using a Web browser, the firewall expects that all your transactions will connect to the server using the accepted http port, which is 80. Now along comes this Java applet NameSender, which is trying to send a datagram to port 8080, which is way outside the range of the protected ports 0-1024. The firewall naturally assumes the worst - that someone has a virus - and it doesn't allow the transaction to happen. This is rather disheartening after learning so much Java, because it would seem that you must give up Java on the server and learn how to write CGI scripts in C or Perl. But as it turns out, despair is not in order. One scenario is part of Sun's grand scheme. If everything goes as planned, Web servers will be equipped with servlet servers. These will take a request from the client (going through the firewall-accepted port 80) and instead of starting up a CGI program they will start up a Java program called a servlet. This is a little application that's designed to run only on the server. A servlet server will automatically start up the servlet to handle the client request, which means you can write all your programs in Java (further enabling the 100 percent pure Java initiative). It is admittedly an appealing idea: once you're comfortable with Java, you don't have to switch to a more primitive language to handle requests on the server. Since it's only for handling requests on the server, the servlet API has no GUI abilities. This fits quite well with NameCollector.java, which doesn't have a GUI anyway. At this writing, a low-cost servlet server was available from java.sun.com. In addition, Sun is encouraging other Web server manufacturers to add servlet capabilities to their servers. Connecting Java to CGI A Java program can send a CGI request to a server just like an HTML page can. As with HTML pages, this request can be either a GET or a POST. In addition, the Java program can intercept the output of the CGI program, so you don't have to rely on the program to format a new page and force the user to back up from one page to another if something goes wrong. In fact, the appearance of the program can be the same as the previous version. It also turns out that the code is simpler, and that CGI isn't difficult to write after all. To solve the general problem, some CGI tools will be created in C++ that will allow you to easily write a CGI program to solve any problem. The benefit to this approach is portability - the example you are about to see will work on any system that supports CGI, and there's no problem with firewalls. This example also works out the basics of creating any connection with applets and CGI programs, so you can easily adapt it to your own projects. Since two fields are being collected, there are no shortcuts because CGI has a particular format for encoding the data in fields. Here you see a little bit of the way that data is encoded to send to CGI. For one thing, spaces are not allowed (since spaces typically separate command-line arguments). Spaces are replaced by '+' signs. This is encoded to: John+%26+Marsha+Smith That is, the special character is turned into a '%' followed by its ASCII value in hex. Fortunately, Java has a tool to perform this encoding for you. It's a static method of the class URLEncoder called encode( ). These are then encoded and printed. The applet The applet is actually considerably simpler than NameSender.java, partly because it's so easy to send a GET request and also because no thread is required to wait for the reply. There are now two fields instead of one, but you'll notice that much of the applet looks familiar, from NameSender.java. Many Web servers are Unix machines (mine runs Linux) that don't traditionally use the.exe extension for their executable programs, but you can call the program anything you want under Unix. By using the .exe extension the program can be tested without change under both Unix and Win32. As before, the applet sets up its user interface (with two fields this time instead of one). The only significant difference occurs inside the action( ) method, which handles the button press. The email name is forced to lower case so all email addresses in the list can be accurately compared (to prevent accidental duplicates based on capitalization). The data from each field is URL-encoded, and then the GET string is assembled in the same way that an HTML page would do it. The constructor makes the connection with the server (and, with Web servers, all the action happens in making the connection, via the string used as the URL). In this case, the URL points to the cgi-bin directory of the current Web site (the base address of the current Web site is produced with getDocumentBase( )). When the Web server sees cgi-bin in a URL, it expects that to be followed by the name of the program inside the cgi-bin directory that you want it to run. Following the program name is a question mark and the argument string that the CGI program will look for in the QUERY_STRING environment variable, as you'll see. Usually when you make any sort of request, you get back (you're forced to accept in return) an HTML page. With Java URL objects, however, you can intercept anything that comes back from the CGI program by getting an InputStream from the URL object. This is performed with the URL openStream( ) method, which is in turn wrapped in a DataInputStream. Then you can read lines, and when readLine( ) returns null the CGI program has finished its output. The CGI program you're about to see returns only one line, a string indicating success or failure (and the details of the failure). This line is captured and placed into the second Label field so the user can see the results. Displaying a Web page from within an applet It's also possible for the applet to display the result of the CGI program as a Web page, just as if it were running in normal HTML mode. You can do this with the following line: getAppletContext().showDocument(u); in which u is the URL object. Here's a simple example that redirects you to another Web page. You can connect to Web servers without knowing much at all about what's going on under the covers. The CGI program in C++ At this point you could follow the previous example and write the CGI program for the server using ANSI C. One argument for doing this is that ANSI C can be found virtually everywhere. However, C++ has become quite ubiquitous, especially in the form of the GNU C++ Compiler4 (g++) that can be downloaded free from the Internet for virtually any platform (and often comes pre-installed with operating systems such as Linux). As you will see, this means that you can get the benefit of object-oriented programming in a CGI program. To avoid throwing too many new concepts at you all at once, this program will not be a pure C++ program; some code will be written in plain C even though C++ alternatives exist. This isn't a significant issue because the biggest benefit in using C++ for this program is the ability to create classes. This program is also interesting because it demonstrates some of the pluses and minuses of C++ in contrast with Java. You'll see some similarities; for example the class keyword. Access control has identical keywords public and private, but they're used differently: they control a block instead of a single method or field (that is, if you say private: each following definition is private until you say public:). Also, when you create a class, all the definitions automatically default to private. One of the reasons for using C++ here is the convenience of the C++ Standard Template Library. Among other things, the STL contains a vector class. This is a C++ template, which means that it will be configured at compile time so it will hold objects of only a particular type (in this case, Pair objects). Thus, the checking happens at compile time and produces a more robust program. In addition, the program can run faster since you don't have to perform run-time casts. The vector template will be used in the creation of CGI_vector, which you'll see is a fairly short definition considering how powerful it is. On the down side, look at the complexity of the definition of Pair in the following code. The project will start with a reusable portion, which consists of Pair and CGI_vector in a C++ header file. The std namespace refers to the Standard C++ library, and vector is in this library so the line is required. The Pair class starts out looking pretty simple: it just holds two (private) character pointers, one for the name and one for the value. The default constructor simply sets these pointers to zero, since in C++ an object's memory isn't automatically zeroed. The second constructor calls the method decodeURLString( ) that produces a decoded string in newly-allocated heap memory. This memory must be managed and destroyed by the object, as you will see in the destructor. The name( ) and value( ) methods produce read-only pointers to the respective fields. The empty( ) method is a way for you to ask the Pair object whether either of its fields are empty; it returns a bool, which is C++'s built-in primitive Boolean data type. The operator bool( ) uses a special case of C++ operator overloading, which allows you to control automatic type conversion. You must define these because they can be quietly called by the compiler when you pass objects in and out of a function (this calls the copy-constructor) or when you assign objects (the assignment operator). That is, you aren't passing the address of the object you're making a copy of the whole object inside the function frame. This isn't an option in Java since you pass only handles, thus there's no copy-constructor in Java. But assignment in C++ means that the entire object is copied. In the copy-constructor, you create new storage and copy the source data, but with the assignment operator you must release the old storage before allocating new storage. This isn't quite the whole story. The Pair class is using char* for nm and val, and the worst-case complexity occurs primarily around pointers. If you use the more modern Standard C++ string class instead of char*, things get much simpler (however, not all compilers have caught up enough to come with string). But even if it sometimes works automatically, C++ programmers must still know the details of copy-construction and assignment. The remainder of the Pair class consists of the two methods decodeURLString( ) and a helper method translateHex( ), which is used by decodeURLString( ). CGI_vector parses and holds an entire CGI GET command. It is inherited from the STL vector, which is instantiated to hold Pairs. Inheritance in C++ is denoted by using a colon at the point you'd say extends in Java. In addition, inheritance defaults to private so you'll almost always need to use the public keyword as was done here. You can also see that CGI_vector has a copy-constructor and an operator=, but they're both declared as private. This is to prevent the compiler from synthesizing the two functions (which it will do if you don't declare them yourself), but it also prevents the client programmer from passing a CGI_vector by value or from using assignment. CGI_vector's job is to take the QUERY_STRING and parse it into name-value pairs, which it will do with the aid of Pair. First it copies the string into locally-allocated memory and keeps track of the starting address with the constant pointer start. These are handed by nextPair( ) to the Pair constructor so nextPair( ) can return the Pair object, which is then added to the vector with push_back( ). When nextPair( ) runs out of QUERY_STRING, it returns zero. So to read that information you have to get the value of QUERY_STRING, which you do using the standard C library function getenv( ). In main( ), notice how simple the act of parsing the QUERY_STRING is: you just hand it to the constructor for the CGI_vector object called query and all the work is done for you. From then on you can pull out the names and values from query as if it were an array. Now it's important to understand something about CGI. A CGI program is handed its input in one of two ways: through QUERY_STRING during a GET (as in this case) or through standard input during a POST. But a CGI program sends its output through standard output, typically using printf( ) in a C program. Where does this output go? Back to the Web server, which decides what to do with it. The server makes this decision based on the content-type header, which means that if the content-type header isn't the first thing it sees, it won't know what to do with the data. Thus, it's essential that you start the output of all CGI programs with the content-type header. In this case, we want the server to feed all the information directly back to the client program (which is our applet, waiting for its reply). Once the server sees this, it will echo all strings right back to the client. So each of the strings you see, three for error conditions and one for a successful add, will end up back at the applet. Adding the email name uses the same code. In the case of the CGI script, however, there isn't an infinite loop - the program just responds and then terminates. Each time a CGI request comes in, the program is started in response to that request, and then it shuts down. Thus there is no possibility of CPU hogging, and the only performance issue concerns starting the program up and opening the file, which are dwarfed by the overhead of the Web server as it handles the CGI request. One of the advantages of this design is that, now that Pair and CGI_vector are defined, most of the work is done for you so you can easily create your own CGI program simply by modifying main( ). Eventually, servlet servers will probably be ubiquitous, but in the meantime C++ is still handy for creating fast CGI programs. What about POST? Using a GET is fine for many applications. However, GET passes its data to the CGI program through an environment variable, and some Web servers can run out of environment space with long GET strings (you should start worrying at about 200 characters). CGI provides a solution for this: POST. With POST, the data is encoded and concatenated the same way as a GET, but POST uses standard input to pass the encoded query string to the CGI program. All you have to do is determine the length of the query string, and this length is stored in the environment variable CONTENT_LENGTH. Once you know the length, you can allocate storage and read the precise number of bytes from standard input. The Pair and CGI_vector from CGITools.h can be used as is for a CGI program that handles POSTs. The following listing shows how simple it is to write such a CGI program. In this example, pure C++ will be used so the stdio.h library will be dropped in favor of iostreams. With iostreams, two predefined objects are available: cin, which connects to standard input, and cout, which connects to standard output. If this pointer is zero, the CONTENT_LENGTH environment variable has not been set, so something is wrong. Otherwise, the character string must be converted to an integer using the ANSI C library function atoi( ). The length is used with new to allocate enough storage to hold the query string (plus its null terminator), and then read( ) is called for cin. The read( ) function takes a pointer to the destination buffer and the number of bytes to read. The query_str is then null-terminated to indicate the end of the character string. At this point, the query string is no different from a GET query string, so it is handed to the constructor for CGI_vector. The different fields in the vector are then available just as in the previous example. To test this program, you must compile it in the cgi-bin directory of your host Web server. Of course, it's a little more interesting to submit the data using an applet. Submitting POST data is a different process, however. After you invoke the CGI program in the usual way, you must make a direct connection to the server so you can feed it the query string. The server then turns around and feeds the query string to the CGI program via standard input. To make a direct connection to the server, you must take the URL you've created and call openConnection( ) to produce a URLConnection. One thing you'll notice is that the results are displayed as lines of text in a TextArea. Why not simply use getAppletContext().showDocument(u)? Well, this is one of those mysteries. The code above works fine, but if you try to use showDocument( ) instead, everything stops working - almost. That is, showDocument( ) does work, but what you get back from POSTtest is Zero CONTENT_LENGTH. So somehow, showDocument( ) prevents the POST query from being passed on to the CGI program. It's difficult to know whether this is a bug that will be fixed, or some lack of understanding on my part (the books I looked at were equally abstruse). In any event, if you can stand to limit yourself to looking at the text that comes back from the CGI program, the above applet works fine. In Java 1.1 this has come to fruition with Java DataBase Connectivity (JDBC). One of the major problems with databases has been the feature wars between the database companies. There is a standard database language, Structured Query Language (SQL-92), but usually you must know which database vendor you're working with despite the standard. JDBC is designed to be platform-independent, so you don't need to worry about the database you're using while you're programming. However, it's still possible to make vendor-specific calls from JDBC so you aren't restricted from doing what you must. JDBC, like many of the APIs in Java, is designed for simplicity. The method calls you make correspond to the logical operations you'd think of doing when gathering data from a database: connect to the database, create a statement and execute the query, and look at the result set. To allow this platform independence, JDBC provides a driver manager that dynamically maintains all the driver objects that your database queries will need. So if you have three different kinds of vendor databases to connect to, you'll need three different driver objects. The driver objects register themselves with the driver manager at the time of loading, and you can force the loading using Class.forName( ). To open a database, you must create a database URL that specifies: 1. That you're using JDBC with jdbc 2. The subprotocol: the name of the driver or the name of a database connectivity mechanism. Since the design of JDBC was inspired by ODBC, the first subprotocol available is the jdbc-odbc bridge, specified by odbc 3. The database identifier. This varies with the database driver used, but it generally provides a logical name that is mapped by the database administration software to a physical directory where the database tables are located. For your database identifier to have any meaning, you must register the name using your database administration software. When you're ready to connect to the database, you call the static method DriverManager.getConnection( ), passing it the database URL, the user name, and a password to get into the database. You get back a Connection object that you can then use to query and manipulate the database. The following example opens a database of contact information and looks for a person's last name as given on the command line. In this example, there is no password protection on the database so the user name and password are empty strings. Once the connection is made with DriverManager.getConnection( ), you can use the resulting Connection object to create a Statement object using the createStatement( ) method. With the resulting Statement, you can call executeQuery( ), passing in a string containing an SQL-92 standard SQL statement. You'll always get a ResultSet object back from executeQuery( ) even if a query results in an empty set (that is, an exception is not thrown). Note that you must call next( ) once before trying to read any record data. If the result set is empty, this first call to next( ) will return false. For each record in the result set, you can select the fields using (among other approaches) the field name as a string. Also note that the capitalization of the field name is ignored - it doesn't matter with an SQL database. You determine the type you'll get back by calling getInt( ), getString( ), getFloat( ), etc. At this point, you've got your database data in Java native format and can do whatever you want with it using ordinary Java code. Getting the example to work With JDBC, understanding the code is relatively simple. The confusing part is making it work on your particular system. The reason this is confusing is that it requires you to figure out how to get your JDBC driver to load properly, and how to set up a database using your database administration software. Of course, this process can vary radically from machine to machine, but the process I used to make it work under 32-bit Windows might give you clues to help you attack your own situation. Step 1: Find the JDBC Driver The program above contains the statement: Class.forName(sun.jdbc.odbc.JdbcOdbcDriver); This implies a directory structure, which is deceiving. With this particular installation of JDK 1.1, there was no file called JdbcOdbcDriver.class, so if you looked at this example and went searching for it you'd be frustrated. Other published examples use a pseudo name, such as myDriver.ClassName, which is less than helpful. In fact, the load statement above for the jdbc-odbc driver (the only one that actually comes with JDK 1.1) appears in only a few places in the online documentation (in particular, a page labeled JDBC-ODBC Bridge Driver). If the load statement above doesn't work, then the name might have been changed as part of a Java version change, so you should hunt through the documentation again. If the load statement is wrong, you'll get an exception at this point. To test whether your driver load statement is working correctly, comment out the code after the statement and up to the catch clause; if the program throws no exceptions it means that the driver is loading properly. Step 2: Configure the database Again, this is specific to 32-bit Windows; you might need to do some research to figure it out for your own platform. First, open the control panel. You might find two icons that say ODBC. You must use the one that says 32bit ODBC, since the other one is for backwards compatibility with 16-bit ODBC software and will produce no results for JDBC. Note that other query tools are also available from other vendors. The most interesting database is one that you're already using. Standard ODBC supports a number of different file formats including such venerable workhorses as DBase. However, it also includes the simple comma-separated ASCII format, which virtually every data tool has the ability to write. In my case, I just took my people database that I've been maintaining for years using various contact-management tools and exported it as a comma-separated ASCII file (these typically have an extension of.csv). In the File DSN section I chose Add, chose the text driver to handle my comma-separated ASCII file, and then un-checked use current directory to allow me to specify the directory where I exported the data file. You'll notice when you do this that you don't actually specify a file, only a directory. That's because a database is typically represented as a collection of files under a single directory (although it could be represented in other forms as well). Each file usually contains a single table, and the SQL statements can produce results that are culled from multiple tables in the database (this is called a join). A database that contains only a single table (like this one) is usually called a flat-file database. Most problems that go beyond the simple storage and retrieval of data generally require multiple tables that must be related by joins to produce the desired results, and these are called relational databases. Step 3: Test the configuration To test the configuration you'll need a way to discover whether the database is visible from a program that queries it. Of course, you can simply run the JDBC program example above up to and including the statement: Connection c = DriverManager.getConnection( dbUrl, user, password); If an exception is thrown, your configuration was incorrect. However, it's useful to get a query-generation tool involved at this point. I used Microsoft Query that came with Microsoft Office, but you might prefer something else. The query tool must know where the database is, and Microsoft Query required that I go to the ODBC Administrator's File DSN tab and add a new entry there, again specifying the text driver and the directory where my database lives. You can name the entry anything you want, but it's helpful to use the same name you used in System DSN. Once you've done this, you will see that your database is available when you create a new query using your query tool. Step 4: Generate your SQL query The query that I created using Microsoft Query not only showed me that my database was there and in good order, but it also automatically created the SQL code that I needed to insert into my Java program. I wanted a query that would search for records that had the last name that was typed on the command line when starting the Java program. So as a starting point, I searched for a specific last name, 'Eckel'. I also wanted to display only those names that had email addresses associated with them. The steps I took to create this query were: 1. Start a new query and use the Query Wizard. Select the people database. From within the table, choose the columns FIRST, LAST, and EMAIL. 3. Under Filter Data, choose LAST and select equals with an argument of Eckel. Click the And radio button. 4. Choose EMAIL and select Is not Null. 5. Under Sort By, choose FIRST. The result of this query will show you whether you're getting what you want. Now you can press the SQL button and without any research on your part, up will pop the correct SQL code, ready for you to cut and paste. It's hard to argue the case for doing this by hand. Step 5: Modify and paste in your query You'll notice that the code above looks different from what's used in the program. That's because the query tool uses full qualification for all of the names, even when there's only one table involved. Instead, it should hunt for the name given as the command-line argument. But for much of your database experimentation and for your first cut, building your own query strings in Java is fine. You can see from this example that by using the tools currently available - in particular the query-building tool - database programming with SQL and JDBC can be quite straightforward. A GUI version of the lookup program It's more useful to leave the lookup program running all the time and simply switch to it and type in a name whenever you want to look someone up. Why the JDBC API seems so complex When you browse the online documentation for JDBC it can seem daunting. What's this all about? As mentioned earlier, databases have seemed from their inception to be in a constant state of turmoil, primarily because the demand for database applications, and thus database tools, is so great. Only recently has there been any convergence on the common language of SQL (and there are plenty of other database languages in common use). In short, you can write simple, transportable SQL, but if you want to optimize speed your coding will multiply tremendously as you investigate the capabilities of a particular vendor's database. This, of course, is not Java's fault. The discrepancies between database products are just something that JDBC tries to help compensate for. There is more JDBC information available in the electronic documents that come as part of the Java 1.1 distribution from Sun. In addition, you can find more in the book JDBC Database Access with Java (Hamilton, Cattel, and Fisher, Addison-Wesley 1997). Other JDBC books are appearing regularly. Remote methods Traditional approaches to executing code on other machines across a network have been confusing as well as tedious and error-prone to implement. The nicest way to think about this problem is that some object happens to live on another machine, and you can send a message to that object and get a result as if the object lived on your local machine. This simplification is exactly what Java 1.1 Remote Method Invocation (RMI) allows you to do. This section walks you through the steps necessary to create your own RMI objects. Remote interfaces RMI makes heavy use of interfaces. When you want to create a remote object, you mask the underlying implementation by passing around an interface. Thus, when the client gets a handle to a remote object, what they really get is an interface handle, which happens to connect to some local stub code that talks across the network. But you don't think about this, you just send messages via your interface handle. When you create a remote interface, you must follow these guidelines: 1. The remote interface must be public (it cannot have package access, that is, it cannot be friendly). Otherwise, a client will get an error when attempting to load a remote object that implements the remote interface. 2. The remote interface must extend the interface java.rmi.Remote. 3. Each method in the remote interface must declare java.rmi.RemoteException in its throws clause in addition to any application-specific exceptions. 4. A remote object passed as an argument or return value (either directly or embedded within a local object) must be declared as the remote interface, not the implementation class. Remember that an interface and all of its methods are automatically public. Implementing the remote interface The server must contain a class that extends UnicastRemoteObject and implements the remote interface. This class can also have additional methods, but only the methods in the remote interface will be available to the client, of course, since the client will get only a handle to the interface, not the class that implements it. You must explicitly define the constructor for the remote object even if you're only defining a default constructor that calls the base-class constructor. You must write it out since it must throw RemoteException. When you're serving RMI objects, at some point in your program you must: 1. Create and install a security manager that supports RMI. The only one available for RMI as part of the Java distribution is RMISecurityManager. 2. Create one or more instances of a remote object. Here, you can see the creation of the PerfectTime object. 3. Register at least one of the remote objects with the RMI remote object registry for bootstrapping purposes. One remote object can have methods that produce handles to other remote objects. This allows you to set it up so the client must go to the registry only once, to get the first remote object. Setting up the registry Here, you see a call to the static method Naming.bind( ). However, this call requires that the registry be running as a separate process on the computer. The name of the registry server is rmiregistry, and under 32-bit Windows you say: start rmiregistry to start it in the background. On Unix, it is: rmiregistry and Like many network programs, the rmiregistry is located at the IP address of whatever machine started it up, but it must also be listening at a port. If you invoke the rmiregistry as above, with no argument, the registry's port will default to 1099. If you want it to be at some other port, you add an argument on the command line to specify the port. But this brings up what can be a frustrating problem if you're expecting to test RMI programs locally the way the network programs have been tested so far in this chapter. In the JDK 1.1.1 release, there are a couple of problems:7 1. localhost does not work with RMI. Thus, to experiment with RMI on a single machine, you must provide the name of the machine. To find out the name of your machine under 32-bit Windows, go to the control panel and select Network. Select the Identification tab, and you'll see your computer name. In my case, I called my computer Colossus (for all the hard disks I've had to put on to hold all the different development systems). It appears that capitalization is ignored. This means that you must connect to your Internet service provider before trying to run the program or you'll get some obscure exception messages. The important thing is that it's a unique name in the registry that the client knows to look for to procure the remote object. If the name is already in the registry, you'll get an AlreadyBoundException. To prevent this, you can always use rebind( ) instead of bind( ), since rebind( ) either adds a new entry or replaces the one that's already there. Even though main( ) exits, your object has been created and registered so it's kept alive by the registry, waiting for a client to come along and request it. As long as the rmiregistry is running and you don't call Naming.unbind( ) on your name, the object will be there. For this reason, when you're developing your code you need to shut down the rmiregistry and restart it when you compile a new version of your remote object. You aren't forced to start up rmiregistry as an external process. This is the equivalent of running rmiregistry 2005 from a command line, but it can often be more convenient when you're developing RMI code since it eliminates the extra steps of starting and stopping the registry. Once you've executed this code, you can bind( ) using Naming as before. Creating stubs and skeletons If you compile and run PerfectTime.java, it won't work even if you have the rmiregistry running correctly. That's because the framework for RMI isn't all there yet. You must first create the stubs and skeletons that provide the network connection operations and allow you to pretend that the remote object is just another local object on your machine. What's going on behind the scenes is complex. Fortunately, you don't have to know any of this, but you do have to create the stubs and skeletons. This is a simple process: you invoke the rmic tool on your compiled code, and it creates the necessary files. So the only requirement is that another step be added to your compilation process. The rmic tool is particular about packages and classpaths, however. PerfectTime.java is in the package c15.Ptime, and even if you invoke rmic in the same directory in which PerfectTime.class is located, rmic won't find the file, since it searches the classpath. When rmic runs successfully, you'll have two new classes in the directory: PerfectTime_Stub.class PerfectTime_Skel.class corresponding to the stub and skeleton. Now you're ready to get the server and client to talk to each other. Using the remote object The whole point of RMI is to make the use of remote objects simple. The only extra thing that you must do in your client program is to look up and fetch the remote interface from the server. From then on, it's just regular Java programming: sending messages to objects. Since you're using a URL, you can also specify a machine on the Internet. What comes back from Naming.lookup( ) must be cast to the remote interface, not to the class. If you use the class instead, you'll get an exception. You can see in the method call t.getPerfectTime( ) that once you have a handle to the remote object, programming with it is indistinguishable from programming with a local object (with one difference: remote methods throw RemoteException). Alternatives to RMI RMI is just one way to create objects that can be distributed across a network. It has the advantage of being a pure Java solution, but if you have a lot of code written in some other language, it might not meet your needs. A more serious treatment of CORBA is given by Java Programming with CORBA by Andreas Vogel and Keith Duddy (John Wiley and Sons, 1997). Summary There's actually a lot more to networking than can be covered in this introductory treatment. Java networking also provides fairly extensive support for URLs, including protocol handlers for different types of content that can be discovered at an Internet site. In addition, an up-and-coming technology is the Servlet Server, which is an Internet server that uses Java to handle requests instead of the slow and rather awkward CGI (Common Gateway Interface) protocol. This means that to provide services on the server side you'll be able to write in Java instead of using some other language that you might not know as well. You'll also get the portability benefits of Java so you won't have to worry about the particular platform the server is hosted on. These and other features are fully and carefully described in Java Network Programming by Elliotte Rusty Harold (O'Reilly, 1997). Exercises 1. Compile and run the JabberServer and JabberClient programs in this chapter. Now edit the files to remove all of the buffering for the input and output, then compile and run them again to observe the results. 2. Create a server that asks for a password, then opens a file and sends the file over the network connection. Create a client that connects to this server, gives the appropriate password, then captures and saves the file. Test the pair of programs on your machine using the localhost (the local loopback IP address 127.0.0.1 produced by calling InetAddress.getByName(null)). 3. Modify the server in Exercise 2 so that it uses multithreading to handle multiple clients. 4. Modify JabberClient so that output flushing doesn't occur and observe the effect. 5. Build on ShowHTML.java to produce an applet that is a password-protected gateway to a particular portion of your Web site. You'll need to look back at the IO stream chapter to remember how to use the Java 1.1 clipboard. In this chapter, the basic concepts of design patterns will be introduced along with several examples. This should whet your appetite to read Design Patterns (a source of what has now become an essential, almost mandatory, vocabulary for OOP programmers). The latter part of this chapter contains an example of the design evolution process, starting with an initial solution and moving through the logic and process of evolving the solution to more appropriate designs. The pattern concept Initially, you can think of a pattern as an especially clever and insightful way of solving a particular class of problems. That is, it looks like a lot of people have worked out all the angles of a problem and have come up with the most general, flexible solution for it. The problem could be one you have seen and solved before, but your solution probably didn't have the kind of completeness you'll see embodied in a pattern. Although they're called design patterns, they really aren't tied to the realm of design. A pattern seems to stand apart from the traditional way of thinking about analysis, design, and implementation. Instead, a pattern embodies a complete idea within a program, and thus it can sometimes appear at the analysis phase or high-level design phase. The basic concept of a pattern can also be seen as the basic concept of program design: adding a layer of abstraction. Whenever you abstract something you're isolating particular details, and one of the most compelling motivations behind this is to separate things that change from things that stay the same. Another way to put this is that once you find some part of your program that's likely to change for one reason or another, you'll want to keep those changes from propagating other changes throughout your code. Not only does this make the code much cheaper to maintain, but it also turns out that it is usually simpler to understand (which results in lowered costs). Once you discover the vector of change, you have the focal point around which to structure your design. So the goal of design patterns is to isolate changes in your code. If you look at it this way, you've been seeing some design patterns already in this book. For example, inheritance can be thought of as a design pattern (albeit one implemented by the compiler). It allows you to express differences in behavior (that's the thing that changes) in objects that all have the same interface (that's what stays the same). Composition can also be considered a pattern, since it allows you to change - dynamically or statically - the objects that implement your class, and thus the way that class works. You've also already seen another pattern that appears in Design Patterns: the iterator (Java 1.0 and 1.1 capriciously calls it the Enumeration; Java 1.2 collections use iterator). This hides the particular implementation of the collection as you're stepping through and selecting the elements one by one. The iterator allows you to write generic code that performs an operation on all of the elements in a sequence without regard to the way that sequence is built. Thus your generic code can be used with any collection that can produce an iterator. The singleton Possibly the simplest design pattern is the singleton, which is a way to provide one and only one instance of an object. You must make all constructors private, and you must create at least one constructor to prevent the compiler from synthesizing a default constructor for you (which it will create as friendly). At this point, you decide how you're going to create your object. Here, it's created statically, but you can also wait until the client programmer asks for one and create it on demand. In any case, the object should be stored privately. You provide access through public methods. Here, getHandle( ) produces the handle to the Singleton object. The rest of the interface (getValue( ) and setValue( )) is the regular class interface. Java also allows the creation of objects through cloning. In this example, making the class final prevents cloning. Since Singleton is inherited directly from Object, the clone( ) method remains protected so it cannot be used (doing so produces a compile-time error). This is also a technique to create a limited pool of objects. In that situation, however, you can be confronted with the problem of sharing objects in the pool. If this is an issue, you can create a solution involving a check-out and check-in of the shared objects. Classifying patterns The Design Patterns book discusses 23 different patterns, classified under three purposes (all of which revolve around the particular aspect that can vary). The three purposes are: 1. Creational: how an object can be created. This often involves isolating the details of object creation so your code isn't dependent on what types of objects there are and thus doesn't have to be changed when you add a new type of object. The aforementioned Singleton is classified as a creational pattern, and later in this chapter you'll see examples of Factory Method and Prototype. 2. Structural: designing objects to satisfy particular project constraints. These work with the way objects are connected with other objects to ensure that changes in the system don't require changes to those connections. 3. Behavioral: objects that handle particular types of actions within a program. These encapsulate processes that you want to perform, such as interpreting a language, fulfilling a request, moving through a sequence (as in an iterator), or implementing an algorithm. This chapter contains examples of the Observer and the Visitor patterns. The Design Patterns book has a section on each of its 23 patterns along with one or more examples for each, typically in C++ but sometimes in Smalltalk. Instead, this chapter will give some examples that should provide you with a decent feel for what patterns are about and why they are so important. The observer pattern The observer pattern solves a fairly common problem: What if a group of objects needs to update themselves when some object changes state? When you change the data, the two views must know to update themselves, and that's what the observer facilitates. It's a common enough problem that its solution has been made a part of the standard java.util library. There are two types of objects used to implement the observer pattern in Java. The Observable class keeps track of everybody who wants to be informed when a change happens, whether the state has changed or not. When someone says OK, everybody should check and potentially update themselves, the Observable class performs this task by calling the notifyObservers( ) method for each one on the list. The notifyObservers( ) method is part of the base class Observable. There are actually two things that change in the observer pattern: the quantity of observing objects and the way an update occurs. That is, the observer pattern allows you to modify both of these without affecting the surrounding code. The following example is similar to the ColorBoxes example from Chapter 14. Boxes are placed in a grid on the screen and each one is initialized to a random color. In addition, each box implements the Observer interface and is registered with an Observable object. When you click on a box, all of the other boxes are notified that a change has been made because the Observable object automatically calls each Observer object's update( ) method. Inside this method, the box checks to see if it's adjacent to the one that was clicked, and if so it changes its color to match the clicked box. But this doesn't work; try it - inside BoxObserver, create an Observable object instead of a BoxObservable object and see what happens: nothing. To get an effect, you must inherit from Observable and somewhere in your derived-class code call setChanged( ). This is the method that sets the changed flag, which means that when you call notifyObservers( ) all of the observers will, in fact, get notified. In the example above setChanged( ) is simply called within notifyObservers( ), but you could use any criterion you want to decide when to call setChanged( ). BoxObserver contains a single Observable object called notifier, and every time an OCBox object is created, it is tied to notifier. Using a combination of code in notifyObservers( ) and update( ) you can work out some fairly complex schemes. It might appear that the way the observers are notified must be frozen at compile time in the notifyObservers( ) method. This means that you could inherit other Observable classes and swap them at run-time if you want to change notification behavior then. Simulating the trash recycler The nature of this problem is that the trash is thrown unclassified into a single bin, so the specific type information is lost. But later, the specific type information must be recovered to properly sort the trash. In the initial solution, RTTI (described in Chapter 11) is used. This is not a trivial design because it has an added constraint. That's what makes it interesting - it's more like the messy problems you're likely to encounter in your work. The extra constraint is that the trash arrives at the trash recycling plant all mixed together. The program must model the sorting of that trash. This is where RTTI comes in: you have a bunch of anonymous pieces of trash, and the program figures out exactly what type they are. The unpacking tool in Chapter 17 takes care of placing it into the correct subdirectory. The reason for doing this is that this chapter rewrites this particular example a number of times and by putting each version in its own package the class names will not clash. Several Vector objects are created to hold Trash handles. Of course, Vectors actually hold Objects so they'll hold anything at all. The reason they hold Trash (or something derived from Trash) is only because you've been careful to not put in anything except Trash. If you do put something wrong into the Vector, you won't get any compile-time warnings or errors - you'll find out only via an exception at run-time. When the Trash handles are added, they lose their specific identities and become simply Object handles (they are upcast). However, because of polymorphism the proper behavior still occurs when the dynamically-bound methods are called through the Enumeration sorter, once the resulting Object has been cast back to Trash. It looks silly to upcast the types of Trash into a collection holding base type handles, and then turn around and downcast. Why not just put the trash into the appropriate receptacle in the first place? In this program it would be easy to repair, but sometimes a system's structure and flexibility can benefit greatly from downcasting. The program satisfies the design requirements: it works. This might be fine as long as it's a one-shot solution. The key to the misuse of RTTI here is that every type is tested. If you're looking for only a subset of types because that subset needs special treatment, that's probably fine. But if you're hunting for every type inside a switch statement, then you're probably missing an important point, and definitely making your code less maintainable. In the next section we'll look at how this program evolved over several stages to become much more flexible. This should prove a valuable example in program design. Improving the design The solutions in Design Patterns are organized around the question What will change as this program evolves? This is usually the most important question that you can ask about any design. This is the promise of object-oriented programming, but it doesn't happen automatically; it requires thought and insight on your part. In this section we'll see how this process can happen during the refinement of a system. The answer to the question What will change? for the recycling system is a common one: more types will be added to the system. The goal of the design, then, is to make this addition of types as painless as possible. In the recycling program, we'd like to encapsulate all places where specific type information is mentioned, so (if for no other reason) any changes can be localized to those encapsulations. It turns out that this process also cleans up the rest of the code considerably. Often the side effect of cleaning up the code will be a system that has better structure and is more flexible. If new types are commonly added, a better solution is a single method that takes all of the necessary information and produces a handle to an object of the correct type, already upcast to a trash object. In Design Patterns this is broadly referred to as a creational pattern (of which there are several). The specific pattern that will be applied here is a variant of the Factory Method. Here, the factory method is a static member of Trash, but more commonly it is a method that is overridden in the derived class. The idea of the factory method is that you pass it the essential information it needs to know to create your object, then stand back and wait for the handle (already upcast to the base type) to pop out as the return value. From then on, you treat the object polymorphically. Thus, you never even need to know the exact type of object that's created. In fact, the factory method hides it from you to prevent accidental misuse. If you want to use the object without polymorphism, you must explicitly use RTTI and casting. But there's a little problem, especially when you use the more complicated approach (not shown here) of making the factory method in the base class and overriding it in the derived classes. What if the information required in the derived class requires more or different arguments? Creating more objects solves this problem. To implement the factory method, the Trash class gets a new method called factory. To hide the creational data, there's a new class called Info that contains all of the necessary information for the factory method to create the appropriate Trash object. Now, if there's a situation in which factory( ) needs more or different information to create a new type of Trash object, the factory( ) interface doesn't need to be changed. The Info class can be changed by adding new data and new constructors, or in the more typical object-oriented fashion of subclassing. The point is that it's now hidden away in one place, and you know to come to this place when you add new types. Of course, if you change the quantity and type of argument, this statement will still need to be modified, but that can be eliminated if the creation of the Info object is automated. For example, a Vector of arguments can be passed into the constructor of an Info object (or directly into a factory( ) call, for that matter). This requires that the arguments be parsed and checked at runtime, but it does provide the greatest flexibility. A pattern for prototyping creation A problem with the design above is that it still requires a central location where all the types of the objects must be known: inside the factory( ) method. If new types are regularly being added to the system, the factory( ) method must be changed for each new type. When you discover something like this, it is useful to try to go one step further and move all of the information about the type - including its creation - into the class representing that type. This way, the only thing you need to do to add a new type to the system is to inherit a single class. To move the information concerning type creation into each specific type of Trash, the prototype pattern (from the Design Patterns book) will be used. The general idea is that you have a master sequence of objects, one of each type you're interested in making. The objects in this sequence are used only for making new objects, using an operation that's not unlike the clone( ) scheme built into Java's root class Object. In this case, we'll name the cloning method tClone( ). When you find one that matches your needs, you clone it. In this scheme there is no hard-coded information for creation. Each object knows how to expose appropriate information and how to clone itself. Thus, the factory( ) method doesn't need to be changed when a new type is added to the system. One approach to the problem of prototyping is to add a number of methods to support the creation of new objects. However, in Java 1.1 there's already support for creating new objects if you have a handle to the Class object. With Java 1.1 reflection (introduced in Chapter 11) you can call a constructor even if you have only a handle to the Class object. This is the perfect solution for the prototyping problem. The list of prototypes will be represented indirectly by a list of handles to all the Class objects you want to create. In addition, if the prototyping fails, the factory( ) method will assume that it's because a particular Class object wasn't in the list, and it will attempt to load it. By loading the prototypes dynamically like this, the Trash class doesn't need to know what types it is working with, so it doesn't need any modifications when you add new types. This allows it to be easily reused throughout the rest of the chapter. The rest of the class supports the prototyping pattern. You first see two inner classes (which are made static, so they are inner classes only for code organization purposes) describing exceptions that can occur. This is followed by a Vector trashTypes, which is used to hold the Class handles. In Trash.factory( ), the String inside the Info object id (a different version of the Info class than that of the prior discussion) contains the type name of the Trash to be created; this String is compared to the Class names in the list. If there's a match, then that's the object to create. Of course, there are many ways to determine what object you want to make. This one is used so that information read in from a file can be turned into objects. Once you've discovered which kind of Trash to create, then the reflection methods come into play. The getConstructor( ) method takes an argument that's an array of Class handles. This array represents the arguments, in their proper order, for the constructor that you're looking for. It's also possible, for a more flexible solution, to call getConstructors( ), which returns an array of the possible constructors. What comes back from getConstructor( ) is a handle to a Constructor object (part of java.lang.reflect). You call the constructor dynamically with the method newInstance( ), which takes an array of Object containing the actual arguments. The process of calling newInstance( ) extracts the double, but you can see it is a bit confusing - an argument might be a double or a Double, but when you make the call you must always pass in a Double. Fortunately, this issue exists only for the primitive types. Once you understand how to do it, the process of creating a new object given only a Class handle is remarkably simple. Reflection also allows you to call methods in this same dynamic fashion. Of course, the appropriate Class handle might not be in the trashTypes list. In this case, the return in the inner loop is never executed and you'll drop out at the end. Here, the program tries to rectify the situation by loading the Class object dynamically and adding it to the trashTypes list. If it still can't be found something is really wrong, but if the load is successful then the factory method is called recursively to try again. Trash subclasses To fit into the prototyping scheme, the only thing that's required of each new subclass of Trash is that it contain a constructor that takes a double argument. Java 1.1 reflection handles everything else. Parsing Trash from an external file The information about Trash objects will be read from an outside file. The trim( ) method removes white space at both ends of a string. However, other types of collections can be used as well. Of course, Vector doesn't implement Fillable, so it won't work. Since Vector is used in most of the examples, it makes sense to add a second overloaded fillBin( ) method that takes a Vector. Alternatively, the collection class can provide its own adapter that implements Fillable. The process of opening the data file containing Trash descriptions and the parsing of that file have been wrapped into the static method ParseTrash.fillBin( ), so now it's no longer a part of our design focus. You will see that throughout the rest of the chapter, no matter what new classes are added, ParseTrash.fillBin( ) will continue to work without change, which indicates a good design. In terms of object creation, this design does indeed severely localize the changes you need to make to add a new type to the system. However, there's a significant problem in the use of RTTI that shows up clearly here. The program seems to run fine, and yet it never detects any cardboard, even though there is cardboard in the list! This happens because of the use of RTTI, which looks for only the types that you tell it to look for. The clue that RTTI is being misused is that every type in the system is being tested, rather than a single type or subset of types. As you will see later, there are ways to use polymorphism instead when you're testing for every type. But if you use RTTI a lot in this fashion, and you add a new type to your system, you can easily forget to make the necessary changes in your program and produce a difficult-to-find bug. So it's worth trying to eliminate RTTI in this case, not just for aesthetic reasons - it produces more maintainable code. Abstracting usage With creation out of the way, it's time to tackle the remainder of the design: where the classes are used. Since it's the act of sorting into bins that's particularly ugly and exposed, why not take that process and hide it inside a class? This is the principle of If you must do something ugly, at least localize the ugliness inside a class. It looks like this: The TrashSorter object initialization must now be changed whenever a new type of Trash is added to the model. How does the statically-coded method deal with the fact that a new type has been added? To solve this, the type information must be removed from sort( ) so that all it needs to do is call a generic method that takes care of the details of type. This, of course, is another way to describe a dynamically-bound method. So sort( ) will simply move through the sequence and call a dynamically-bound method for each Vector. Since the job of this method is to grab the pieces of trash it is interested in, it's called grab(Trash). The structure now looks like: TrashSorter needs to call each grab( ) method and get a different result depending on what type of Trash the current Vector is holding. That is, each Vector must be aware of the type it holds. The classic approach to this problem is to create a base Trash bin class and inherit a new derived class for each different type you want to hold. If Java had a parameterized type mechanism that would probably be the most straightforward approach. But rather than hand-coding all the classes that such a mechanism should be building for us, further observation can produce a better approach. But what it does is strictly dependent on the type, and nothing else. This could be interpreted as a different state, and since Java has a class to represent type (Class) this can be used to determine the type of Trash a particular Tbin will hold. The constructor for this Tbin requires that you hand it the Class of your choice. This tells the Vector what type it is supposed to hold. Then the grab( ) method uses Class BinType and RTTI to see if the Trash object you've handed it matches the type it's supposed to grab. Here is the whole program. The commented numbers (e.g. (*1*) ) mark sections that will be described following the code. 2. sortBin( ) allows you to pass an entire Tbin in, and it moves through the Tbin, picks out each piece of Trash, and sorts it into the appropriate specific Tbin. Notice the genericity of this code: it doesn't change at all if new types are added. If the bulk of your code doesn't need changing when a new type is added (or some other change occurs) then you have an easily-extensible system. 3. Now you can see how easy it is to add a new type. Few lines must be changed to support the addition. If it's really important, you can squeeze out even more by further manipulating the design. 4. One method call causes the contents of bin to be sorted into the respective specifically-typed bins. Multiple dispatching The above design is certainly satisfactory. Adding new types to the system consists of adding or modifying distinct classes without causing code changes to be propagated throughout the system. In addition, RTTI is not misused as it was in RecycleA.java. However, it's possible to go one step further and take a purist viewpoint about RTTI and say that it should be eliminated altogether from the operation of sorting the trash into bins. The previous examples first sorted by type, then acted on sequences of elements that were all of a particular type. But whenever you find yourself picking out particular types, stop and think. The whole idea of polymorphism (dynamically-bound method calls) is to handle type-specific information for you. So why are you hunting for types? The answer is something you probably don't think about: Java performs only single dispatching. That is, if you are performing an operation on more than one object whose type is unknown, Java will invoke the dynamic binding mechanism on only one of those types. This doesn't solve the problem, so you end up detecting some types manually and effectively producing your own dynamic binding behavior. The solution is called multiple dispatching, which means setting up a configuration such that a single method call produces more than one dynamic method call and thus determines more than one type in the process. To get this effect, you need to work with more than one type hierarchy: you'll need a type hierarchy for each dispatch. The following example works with two hierarchies: the existing Trash family and a hierarchy of the types of trash bins that the trash will be placed into. This second hierarchy isn't always obvious and in this case it needed to be created in order to produce multiple dispatching (in this case there will be only two dispatches, which is referred to as double dispatching). Implementing the double dispatch Remember that polymorphism can occur only via method calls, so if you want double dispatching to occur, there must be two method calls: one used to determine the type within each hierarchy. In the Trash hierarchy there will be a new method called addToBin( ), which takes an argument of an array of TypedBin. It uses this array to step through and try to add itself to the appropriate bin, and this is where you'll see the double dispatch. The new hierarchy is TypedBin, and it contains its own method called add( ) that is also used polymorphically. But here's an additional twist: add( ) is overloaded to take arguments of the different types of trash. So an essential part of the double dispatching scheme also involves overloading. Redesigning the program produces a dilemma: it's now necessary for the base class Trash to contain an addToBin( ) method. One approach is to copy all of the code and change the base class. Another approach, which you can take when you don't have control of the source code, is to put the addToBin( ) method into an interface, leave Trash alone, and inherit new specific types of Aluminum, Paper, Glass, and Cardboard. This is the approach that will be taken here. Most of the classes in this design must be public, so they are placed in their own files. But notice the argument: this. The type of this is different for each subclass of Trash, so the code is different. During the call to add( ), this information is passed via the type of this. The compiler resolves the call to the proper overloaded version of add( ). That is the second dispatch. In each of the subclasses of TypedBin, only one overloaded method is overridden, according to the type of bin that's being created. For example, CardboardBin overrides add(DDCardboard). The overridden method adds the trash object to its collection and returns true, while all the rest of the add( ) methods in CardboardBin continue to return false, since they haven't been overridden. This is another case in which a parameterized type mechanism in Java would allow automatic generation of code. You can see that once the structure is set up, sorting into the various TypedBins is remarkably easy. In addition, the efficiency of two dynamic method calls is probably better than any other way you could sort. Notice the ease of use of this system in main( ), as well as the complete independence of any specific type information within main( ). All other methods that talk only to the Trash base-class interface will be equally invulnerable to changes in Trash types. The visitor pattern Now consider applying a design pattern with an entirely different goal to the trash-sorting problem. For this pattern, we are no longer concerned with optimizing the addition of new types of Trash to the system. Indeed, this pattern makes adding a new type of Trash more complicated. The assumption is that you have a primary class hierarchy that is fixed; perhaps it's from another vendor and you can't make changes to that hierarchy. However, you'd like to add new polymorphic methods to that hierarchy, which means that normally you'd have to add something to the base class interface. So the dilemma is that you need to add methods to the base class, but you can't touch the base class. How do you get around this? The design pattern that solves this kind of problem is called a visitor (the final one in the Design Patterns book), and it builds on the double dispatching scheme shown in the last section. The visitor pattern allows you to extend the interface of the primary type by creating a separate class hierarchy of type Visitor to virtualize the operations performed upon the primary type. The objects of the primary type simply accept the visitor, then call the visitor's dynamically-bound method. This configuration means that new functionality can be added to the system in the form of new subclasses of Visitor. The Trash hierarchy doesn't need to be touched. This is the prime benefit of the visitor pattern: you can add new polymorphic functionality to a class hierarchy without touching that hierarchy (once the accept( ) methods have been installed). Note that the benefit is helpful here but not exactly what we started out to accomplish, so at first blush you might decide that this isn't the desired solution. But look at one thing that's been accomplished: the visitor solution avoids sorting from the master Trash sequence into individual typed sequences. Thus, you can leave everything in the single master sequence and simply pass through that sequence using the appropriate visitor to accomplish the goal. Although this behavior seems to be a side effect of visitor, it does give us what we want (avoiding RTTI). The double dispatching in the visitor pattern takes care of determining both the type of Trash and the type of Visitor. In the following example, there are two implementations of Visitor: PriceVisitor to both determine and sum the price, and WeightVisitor to keep track of the weights. You can see all of this implemented in the new, improved version of the recycling program. Now there's only a single Trash bin. The two Visitor objects are accepted into every element in the sequence, and they perform their operations. The visitors keep their own internal data to tally the total weights and prices. Finally, there's no run-time type identification other than the inevitable cast to Trash when pulling things out of the sequence. This, too, could be eliminated with the implementation of parameterized types in Java. More coupling? There's a lot more code here, and there's definite coupling between the Trash hierarchy and the Visitor hierarchy. However, there's also high cohesion within the respective sets of classes: they each do only one thing (Trash describes Trash, while Visitor describes actions performed on Trash), which is an indicator of a good design. Of course, in this case it works well only if you're adding new Visitors, but it gets in the way when you add new types of Trash. Low coupling between classes and high cohesion within a class is definitely an important design goal. Applied mindlessly, though, it can prevent you from achieving a more elegant design. It seems that some classes inevitably have a certain intimacy with each other. These often occur in pairs that could perhaps be called couplets, for example, collections and iterators (Enumerations). The Trash-Visitor pair above appears to be another such couplet. RTTI considered harmful? Various designs in this chapter attempt to remove RTTI, which might give you the impression that it's considered harmful (the condemnation used for poor, ill-fated goto, which was thus never put into Java). This isn't true; it is the misuse of RTTI that is the problem. The reason our designs removed RTTI is because the misapplication of that feature prevented extensibility, while the stated goal was to be able to add a new type to the system with as little impact on surrounding code as possible. However, RTTI doesn't automatically create non-extensible code. Let's revisit the trash recycler once more. This time, a new tool will be introduced, which I call a TypeMap. It contains a Hashtable that holds Vectors, but the interface is simple: you can add( ) a new object, and you can get( ) a Vector containing all the objects of a particular type. The keys for the contained Hashtable are the types in the associated Vector. The beauty of this design (suggested by Larry O'Brien) is that the TypeMap dynamically adds a new pair whenever it encounters a new type, so whenever you add a new type to the system (even if you add the new type at run-time), it adapts. It contains a Hashtable, and the add( ) method does most of the work. When you add( ) a new object, the handle for the Class object for that type is extracted. This is used as a key to determine whether a Vector holding objects of that type is already present in the Hashtable. If so, that Vector is extracted and the object is added to the Vector. If not, the Class object and a new Vector are added as a key-value pair. You can get an Enumeration of all the Class objects from keys( ), and use each Class object to fetch the corresponding Vector with get( ). And that's all there is to it. The filler( ) method is interesting because it takes advantage of the design of ParseTrash.fillBin( ), which doesn't just try to fill a Vector but instead anything that implements the Fillable interface with its addTrash( ) method. You never need a named class to implement Fillable, you just need a handle to an object of that class, thus this is an appropriate use of anonymous inner classes. An interesting thing about this design is that even though it wasn't created to handle the sorting, fillBin( ) is performing a sort every time it inserts a Trash object into bin. Much of class DynaTrash should be familiar from the previous examples. This time, instead of placing the new Trash objects into a bin of type Vector, the bin is of type TypeMap, so when the trash is thrown into bin it's immediately sorted by TypeMap's internal sorting mechanism. This is certainly the smallest solution to the problem, and arguably the most elegant as well. It does rely heavily on RTTI, but notice that each key-value pair in the Hashtable is looking for only one type. In addition, there's no way you can forget to add the proper code to this system when you add a new type, since there isn't any code you need to add. Summary Coming up with a design such as TrashVisitor.java that contains a larger amount of code than the earlier designs can seem at first to be counterproductive. It pays to notice what you're trying to accomplish with various designs. Design patterns in general strive to separate the things that change from the things that stay the same. The things that change can refer to many different kinds of changes. Perhaps the change occurs because the program is placed into a new environment or because something in the current environment changes (this could be: The user wants to add a new shape to the diagram currently on the screen). Or, as in this case, the change could be the evolution of the code body. While previous versions of the trash-sorting example emphasized the addition of new types of Trash to the system, TrashVisitor.java allows you to easily add new functionality without disturbing the Trash hierarchy. There's more code in TrashVisitor.java, but adding new functionality to Visitor is cheap. If this is something that happens a lot, then it's worth the extra effort and code to make it happen more easily. The discovery of the vector of change is no trivial matter; it's not something that an analyst can usually detect before the program sees its initial design. The necessary information will probably not appear until later phases in the project: sometimes only at the design or implementation phases do you discover a deeper or more subtle need in your system. In the case of adding new types (which was the focus of most of the recycle examples) you might realize that you need a particular inheritance hierarchy only when you are in the maintenance phase and you begin extending the system! One of the most important things that you'll learn by studying design patterns seems to be an about-face from what has been promoted so far in this book. That is: OOP is all about polymorphism. This statement can produce the two-year-old with a hammer syndrome (everything looks like a nail). Put another way, it's hard enough to get polymorphism, and once you do, you try to cast all your designs into that one particular mold. What design patterns say is that OOP isn't just about polymorphism. But design patterns in general show other ways to accomplish the basic goal, and once your eyes have been opened to this you will begin to search for more creative designs. Since the Design Patterns book came out and made such an impact, people have been searching for other patterns. You can expect to see more of these appear as time goes on. Exercises 1. Using SingletonPattern.java as a starting point, create a class that manages a fixed number of its own objects. 2. Add a class Plastic to TrashVisitor.java. 3. Add a class Plastic to DynaTrash.java. Most of these projects are significantly more complex than the examples in the rest of the book, and they often demonstrate new techniques and uses of class libraries. Text processing If you come from a C or C++ background, you might be skeptical at first of Java's power when it comes to handling text. Indeed, one drawback is that execution speed is slower and that could hinder some of your efforts. However, the tools (in particular the String class) are quite powerful, as the examples in this section show (and performance improvements have been promised for Java). As you'll see, these examples were created to solve problems that arose in the creation of this book. However, they are not restricted to that and the solutions they offer can easily be adapted to other situations. In addition, they show the power of Java in an area that has not previously been emphasized in this book. In my previous book, I had a system that allowed me to automatically incorporate tested code files into the book. In this book, however, I discovered that it was often easier to paste the code into the book once it was initially tested and, since it's hard to get right the first time, to perform edits to the code within the book. But how to extract it and test the code? This program is the answer, and it could come in handy when you set out to solve a text processing problem. It also demonstrates many of the String class features. I first save the entire book in ASCII text format into a separate file. The CodePackager program has two modes (which you can see described in usageString): if you use the -p flag, it expects to see an input file containing the ASCII text from the book. It will go through this file and use the comment tag marks to extract the code, and it uses the file name on the first line to determine the name of the file. In addition, it looks for the package statement in case it needs to put the file into a special directory (chosen via the path indicated by the package statement). But that's not all. It also watches for the change in chapters by keeping track of the package names. Since all packages for each chapter begin with c02, c03, c04, etc. As each file is extracted, it is placed into a SourceCodeFile object that is then placed into a collection. If you invoke CodePackager without the -p flag it expects a packed file as input, which it will then extract into separate files. So the -p flag means that the extracted files will be found packed into this single file. Why bother with the packed file? Because different computer platforms have different ways of storing text information in files. A big issue is the end-of-line character or characters, but other issues can also exist. That is, Java handles all of the platform-specific details, which is a large part of the promise of Java. So the -p flag stores everything into a single file in a universal format. You download this file and the Java program from the Web, and when you run CodePackager on this file without the -p flag the files will all be extracted to appropriate places on your system. In addition, there's a sanity check: an empty file is placed in each subdirectory; the name of that file indicates how many files you should find in that subdirectory. Since this is the first program in the chapter, the package statement is necessary to tell CodePackager that the chapter has changed, but putting it in a package would be a problem. When you create a package, you tie the resulting program to a particular directory structure, which is fine for most of the examples in this book. Here, however, the CodePackager program must be compiled and run from an arbitrary directory, so the package statement is commented out. It will still look like an ordinary package statement to CodePackager, though, since the program isn't sophisticated enough to detect multi-line comments. The first, Pr, is similar to the ANSI C library perror, since it prints an error message (but also exits the program). The second class encapsulates the creation of files, a process that was shown in Chapter 10 as one that rapidly becomes verbose and annoying. In Chapter 10, the proposed solution created new classes, but here static method calls are used. Within those methods the appropriate exceptions are caught and dealt with. These methods make the rest of the code much cleaner to read. The first class that helps solve the problem is SourceCodeFile, which represents all the information (including the contents, file name, and directory) for one source code file in the book. The subdirectory name for the current chapter is kept in the field chapter, which is initialized to c02. Building a packed file The first constructor is used to extract a file from the ASCII text version of this book. The calling code (which appears further down in the listing) reads each line in until it finds one that matches the beginning of a listing. At that point, it creates a new SourceCodeFile object, passing it the first line (which has already been read by the calling code) and the BufferedReader object from which to extract the rest of the source code listing. At this point, you begin to see heavy use of the String methods. To extract the file name, the overloaded version of substring( ) is called that takes the starting offset and goes to the end of the String. This starting index is produced by finding the length( ) of the startMarker. Notice there is also an overloaded version of indexOf( ) that takes a String instead of a character. Once the file name is parsed and stored, the first line is placed into the contents String (which is used to hold the entire text of the source code listing). At this point, the rest of the lines are read and concatenated into the contents String. It's not quite that simple, since certain situations require special handling. One case is error checking: if you run into a startMarker, it means that no end marker was placed at the end of the listing that's currently being collected. This is an error condition that aborts the program. The second special case is the package keyword. Although Java is a free-form language, this program requires that the package keyword be at the beginning of the line. When the package keyword is seen, the package name is extracted by looking for the space at the beginning and the semicolon at the end. This is probably true on all systems, but it's a place to look if there are problems. The default behavior is to concatenate each line to contents, along with the end-of-line string, until the endMarker is discovered, which indicates that the constructor should terminate. If the end of the file is encountered before the endMarker is seen, that's an error. Extracting from a packed file The second constructor is used to recover the source code files from a packed file. Here, the calling method doesn't have to worry about skipping over the intermediate text. The file contains all the source-code files, placed end-to-end. All you need to hand to this constructor is the BufferedReader where the information is coming from, and the constructor takes it from there. There is some meta-information, however, at the beginning of each listing, and this is denoted by the packMarker. If the packMarker isn't there, it means the caller is mistakenly trying to use this constructor where it isn't appropriate. Once the packMarker is found, it is stripped off and the directory name (terminated by a '#') and the file name (which goes to the end of the line) are extracted. In both cases, the old separator character is replaced by the one that is current to this machine using the String replace( ) method. The old separator is placed at the beginning of the packed file, and you'll see how that is extracted later in the listing. The rest of the constructor is quite simple. It reads and concatenates each line to the contents until the endMarker is found. All writePacked( ) needs is the DataOutputStream, which was opened elsewhere, and represents the file that's being written. It puts the header information on the first line and then calls writeBytes( ) to write contents in a universal format. When writing the Java source file, the file must be created. This is done via IO.psOpen( ), handing it a File object that contains not only the file name but also the path. But the question now is: does this path exist? The user has the option of placing all the source code directories into a completely different subdirectory, which might not even exist. So before each file is written, File.mkdirs( ) is called with the path that you want to write the file into. This will make the entire path all at once. Containing the entire collection of listings It's convenient to organize the listings as subdirectories while the whole collection is being built in memory. One reason is another sanity check: as each subdirectory of listings is created, an additional file is added whose name contains the number of files in that directory. Thus, instead of mapping a key to a single value, the multimap maps a key to a set of values via the associated Vector. Although this sounds complex, it's remarkably straightforward to implement. You'll see that most of the size of the DirMap class is due to the portions that write to files, not to the multimap implementation. There are two ways you can make a DirMap: the default constructor assumes that you want the directories to branch off of the current one, and the second constructor lets you specify an alternate absolute path for the starting directory. The add( ) method is where quite a bit of dense action occurs. First, the directory( ) is extracted from the SourceCodeFile you want to add, and then the Hashtable is examined to see if it contains that key already. If not, a new Vector is added to the Hashtable and associated with that key. At this point, the Vector is there, one way or another, and it is extracted so the SourceCodeFile can be added. Because Vectors can be easily combined with Hashtables like this, the power of both is amplified. Writing a packed file involves opening the file to write (as a DataOutputStream so the data is universally recoverable) and writing the header information about the old separator on the first line. Next, an Enumeration of the Hashtable keys is produced and stepped through to select each directory and to fetch the Vector associated with that directory so each SourceCodeFile in that Vector can be written to the packed file. Writing the Java source files to their directories in write( ) is almost identical to writePackedFile( ) since both methods simply call the appropriate method in SourceCodeFile. Here, however, the root path is passed into SourceCodeFile.writeFile( ) and when all the files have been written the additional file with the name containing the number of files is also written. The main program The previously described classes are used within CodePackager. First you see the usage string that gets printed whenever the end user invokes the program incorrectly, along with the usage( ) method that calls it and exits the program. All main( ) does is determine whether you want to create a packed file or extract from one, then it ensures the arguments are correct and calls the appropriate method. When a packed file is created, it's assumed to be made in the current directory, so the DirMap is created using the default constructor. After the file is opened each line is read and examined for particular conditions: 1. If the line starts with the starting marker for a source code listing, a new SourceCodeFile object is created. The constructor reads in the rest of the source listing. The handle that results is directly added to the DirMap. 2. If the line starts with the end marker for a source code listing, something has gone wrong, since end markers should be found only by the SourceCodeFile constructor. When extracting a packed file, the extraction can be into the current directory or into an alternate directory, so the DirMap object is created accordingly. The file is opened and the first line is read. The old file path separator information is extracted from this line. Then the input is used to create the first SourceCodeFile object, which is added to the DirMap. New SourceCodeFile objects are created and added as long as they contain a file. It opens each.java file in the current directory and extracts all the class names and identifiers, then shows you if any of them don't meet the Java style. For the program to operate correctly, you must first build a class name repository to hold all the class names in the standard Java library. You do this by moving into all the source code subdirectories for the standard Java library and running ClassScanner in each subdirectory. Provide as arguments the name of the repository file (using the same path and name each time) and the -a command-line option to indicate that the class names should be added to the repository. To use the program to check your code, run it and hand it the path and name of the repository to use. It will check all the classes and identifiers in the current directory and tell you which ones don't follow the typical Java capitalization style. You should be aware that the program isn't perfect; there a few times when it will point out what it thinks is a problem but on looking at the code you'll see that nothing needs to be changed. This is a little annoying, but it's still much easier than trying to find all these cases by staring at your code. As in the previous example, it uses a Hashtable (this time with inheritance) with the key as the single string that's mapped onto the Vector value. The add( ) method simply checks to see if there's a key already in the Hashtable, and if not it puts one there. The getVector( ) method produces a Vector for a particular key, and printValues( ), which is primarily useful for debugging, prints out all the values Vector by Vector. To keep life simple, the class names from the standard Java libraries are all put into a Properties object (from the standard Java library). Remember that a Properties object is a Hashtable that holds only String objects for both the key and value entries. However, it can be saved to disk and restored from disk in one method call, so it's ideal for the repository of names. Actually, we need only a list of names, and a Hashtable can't accept null for either its key or its value entry. So the same object will be used for both the key and the value. For the classes and identifiers that are discovered for the files in a particular directory, two MultiStringMaps are used: classMap and identMap. Also, when the program starts up it loads the standard class name repository into the Properties object called classes, and when a new class name is found in the local directory that is also added to classes as well as to classMap. The default constructor for ClassScanner creates a list of file names (using the JavaFilter implementation of FilenameFilter, as described in Chapter 10). Then it calls scanListing( ) for each file name. Inside scanListing( ) the source code file is opened and turned into a StreamTokenizer. In the documentation, passing true to slashStarComments( ) and slashSlashComments( ) is supposed to strip those comments out, but this seems to be a bit flawed (it doesn't quite work in Java 1.0). Instead, those lines are commented out and the comments are extracted by another method. This is also true for dots ('.'), since we want to have the method calls pulled apart into individual identifiers. However, the underscore, which is ordinarily treated by StreamTokenizer as an individual character, should be left as part of identifiers since it appears in such static final values as TT_EOF etc., used in this very program. The wordChars( ) method takes a range of characters you want to add to those that are left inside a token that is being parsed as a word. Finally, when parsing for one-line comments or discarding a line we need to know when an end-of-line occurs, so by calling eolIsSignificant(true) the eol will show up rather than being absorbed by the StreamTokenizer. The rest of scanListing( ) reads and reacts to tokens until the end of the file, signified when nextToken( ) returns the final static value StreamTokenizer.TT_EOF. The only other situation we're interested in here is if it's a word, of which there are some special cases. If the word is class or interface then the next token represents a class or interface name, and it is put into classes and classMap. If the word is import or package, then we don't want the rest of the line. Anything else must be an identifier (which we're interested in) or a keyword (which we're not, but they're all lowercase anyway so it won't spoil things to put those in). These are added to identMap. The discardLine( ) method is a simple tool that looks for the end of a line. Note that any time you get a new token, you must check for the end of the file. The eatComments( ) method is called whenever a forward slash is encountered in the main parsing loop. However, that doesn't necessarily mean a comment has been found, so the next token must be extracted to see if it's another forward slash (in which case the line is discarded) or an asterisk. But if it's neither of those, it means the token you've just pulled out is needed back in the main parsing loop! Fortunately, the pushBack( ) method allows you to push back the current token onto the input stream so that when the main parsing loop calls nextToken( ) it will get the one you just pushed back. For convenience, the classNames( ) method produces an array of all the names in the classes collection. This method is not used in the program but is helpful for debugging. The next two methods are the ones in which the actual checking takes place. In checkClassNames( ), the class names are extracted from the classMap (which, remember, contains only the names in this directory, organized by file name so the file name can be printed along with the errant class name). This is accomplished by pulling each associated Vector and stepping through that, looking to see if the first character is lower case. If so, the appropriate error message is printed. In checkIdentNames( ), a similar approach is taken: each identifier name is extracted from identMap. If the name is not in the classes list, it's assumed to be an identifier or keyword. A special case is checked: if the identifier length is 3 or more and all the characters are uppercase, this identifier is ignored because it's probably a static final value such as TT_EOF. Of course, this is not a perfect algorithm, but it assumes that you'll eventually notice any all-uppercase identifiers that are out of place. Instead of reporting every identifier that starts with an uppercase character, this method keeps track of which ones have already been reported in a Vector called reportSet( ). This treats the Vector as a set that tells you whether an item is already in the set. The item is produced by concatenating the file name and identifier. If the element isn't in the set, it's added and then the report is made. In both cases it makes a ClassScanner object. Whether you're building a repository or using one, you must try to open the existing repository. By making a File object and testing for existence, you can decide whether to open the file and load( ) the Properties list classes inside ClassScanner. In this case, an output file is opened and the method Properties.save( ) is used to write the list into a file, along with a string that provides header file information. A method lookup tool Chapter 11 introduced the Java 1.1 concept of reflection and used that feature to look up methods for a particular class - either the entire list of methods or a subset of those whose names match a keyword you provide. The magic of this is that it can automatically show you all the methods for a class without forcing you to walk up the inheritance hierarchy examining the base classes at each level. Thus, it provides a valuable timesaving tool for programming: because the names of most Java method names are made nicely verbose and descriptive, you can search for the method names that contain a particular word of interest. When you find what you think you're looking for, check the online documentation. However, by Chapter 11 you hadn't seen the AWT, so that tool was developed as a command-line application. Dynamically narrows your search. As with many of the GUI programs in this book, this is created to perform both as an application and as an applet. Also, the StripQualifiers class is exactly the same as it was in Chapter 11. The GUI contains a TextField name in which you can enter the fully-qualified class name you want to look up, and another one, searchFor, in which you can enter the optional text to search for within the list of methods. The Checkbox allows you to say whether you want to use the fully-qualified names in the output or if you want the qualification stripped off. Finally, the results are displayed in a TextArea. You'll notice that there are no buttons or other components by which to indicate that you want the search to start. That's because both of the TextFields and the Checkbox are monitored by their listener objects. Whenever you make a change, the list is immediately updated. If you change the text within the name field, the new text is captured in class NameL. If the text isn't empty, it is used inside Class.forName( ) to try to look up the class. As you're typing, of course, the name will be incomplete and Class.forName( ) will fail, which means that it throws an exception. Each of the objects in these arrays is turned into a String via toString( ) (this produces the complete method or constructor signature) and both lists are combined into n, a single String array. The array n is a member of class DisplayMethods and is used in updating the display whenever reDisplay( ) is called. If you change the Checkbox or searchFor components, their listeners simply call reDisplay( ). The result set is either copied directly from n if there is no find word, or conditionally copied from the Strings in n that contain the find word. Finally, the strip Checkbox is interrogated to see if the user wants the names to be stripped (the default is yes). If so, StripQualifiers.strip( ) does the job; if not, the list is simply displayed. In init( ), you might think that there's a lot of busy work involved in setting up the layout. You might find that you'll keep this tool running while you're programming, since it provides one of the best first lines of attack when you're trying to figure out what method to call. 2. Alignment: Follow the average heading of local flockmates. 3. Cohesion: Move toward the center of the group of local flockmates. More elaborate models can include obstacles and the ability for the animals to predict collisions and avoid them, so the animals can flow around fixed objects in the environment. In addition, the animals might also be given a goal, which can cause the herd to follow a desired path. For simplicity, obstacle avoidance and goal-seeking is not included in the model presented here. Emergence means that, despite the limited nature of computers and the simplicity of the steering rules, the result seems realistic. That is, remarkably lifelike behavior emerges from this simple model. It also makes the point that while Java must certainly have its limits, those limits are primarily relegated to performance. Not only does it seem possible to express just about everything you can imagine, but Java seems oriented toward making that expression easy to write and read. Therefore you don't run into the wall of complexity that often occurs with languages that are more trivial to use than Java (at least they seem that way, at first). Exercises 1. (Challenging) Rewrite FieldOBeasts.java so that its state can be persistent. Implement buttons to allow you to save and recall different state files and continue running them where they left off. Use CADState.java from Chapter 10 as an example of how to do this. 2. (Term project) Taking FieldOBeasts.java as a starting point, build an automobile traffic simulation system. 3. (Term project) Using ClassScanner.java as a starting point, build a tool that points out methods and fields that are defined but never used. 4. (Term project) Using JDBC, build a contact management program using a flat-file database containing names, addresses, telephone numbers, email addresses, etc. You should be able to easily add new names to the database. When typing in the name to be looked up, use automatic name completion as shown in VLookup.java in Chapter 15. The Java language and its standard API are rich enough to write full-fledged applications. Interfacing with non-Java code requires dedicated support in the compiler and in the Virtual Machine, and additional tools to map the Java code to the non-Java code. This fragmentation among different vendors implies serious drawbacks for the programmer. If a Java application must call native methods, the programmer might need to implement different versions of the native methods depending on the platform the application will run on. The programmer might actually need different versions of the Java code as well as different Java virtual machines. Another solution is CORBA (Common Object Request Broker Architecture), an integration technology developed by the OMG (Object Management Group, a non-profit consortium of companies). CORBA is not part of any language, but is a specification for implementing a common communication bus and services that allow interoperability among objects implemented in different languages. This communication bus, called an ORB (Object Request Broker), is a product implemented by third-party vendors, but it is not part of the Java language specification. This is not an in-depth treatment, and in some cases you're assumed to have partial knowledge of the related concepts and techniques. But in the end, you should be able to compare the different approaches and choose the one that is most appropriate to the problem you want to solve. The Java Native Interface JNI is a fairly rich programming interface that allows you to call native methods from a Java application. It was added in Java 1.1, maintaining a certain degree of compatibility with its Java 1.0 equivalent, the native method interface (NMI). NMI has design characteristics that make it unsuitable for adoption in all virtual machines. For this reason, future versions of the language might no longer support NMI, and it will not be covered here. Calling a native method We'll start with a simple example: a Java program that calls a native method, which in turn calls the Win32 MessageBox( ) API function to display a graphical text box. System.loadLibrary( ) loads a DLL in memory and links to it. The DLL must be in your system path or in the directory containing the Java class file. The file name extension is automatically added by the JVM depending on the platform. The C header file generator: javah Now compile your Java source file and run javah on the resulting.class file. The first #include directive includes jni.h, a header file that, among other things, defines the types that you can see used in the rest of the file. JNIEXPORT and JNICALL are macros that expand to match platform-specific directives; JNIEnv, jobject and jstring are JNI data type definitions. Name mangling and function signatures JNI imposes a naming convention (called name mangling) on native methods; this is important, since it's part of the mechanism by which the virtual machine links Java calls to native methods. Basically, all native methods start with the word Java, followed by the name of the class in which the Java native declaration appears, followed by the name of the Java method; the underscore character is used as a separator. If the Java native method is overloaded, then the function signature is appended to the name as well; you can see the native signature in the comments preceding the prototype. For more information about name mangling and native method signatures, please refer to the JNI documentation. Implementing your DLL At this point, all you have to do is write a C or C++ source file that includes the javah-generated header file and implements the native method, then compile it and generate a dynamic link library. This part is platform-dependent, and I'll assume that you know how to create a DLL. The code below implements the native method by calling a Win32 API. It is then compiled and linked into a file called MsgImpl.dll (for Message Implementation). The arguments that are passed into the native method are the gateway back into Java. The first, of type JNIEnv, contains all the hooks that allow you to call back into the JVM. For non-static methods like the example above (also called instance methods), the second argument is the equivalent of the this pointer in C++ and similar to this in Java: it's a reference to the object that called the native method. For static methods, it's a reference to the Class object where the method is implemented. The remaining arguments represent the Java objects passed into the native method call. Primitives are also passed in this way, but they come in by value. In the following sections we'll explain this code by looking at how to access and control the JVM from inside a native method. Accessing JNI functions: The JNIEnv argument JNI functions are those that the programmer uses to interact with the JVM from inside a native method. As you can see in the example above, every JNI native method receives a special argument as its first parameter: the JNIEnv argument, which is a pointer to a special JNI data structure of type JNIEnv_. One element of the JNI data structure is a pointer to an array generated by the JVM; each element of this array is a pointer to a JNI function. The JNI functions can be called from the native method by dereferencing these pointers (it's simpler than it sounds). Every JVM provides its own implementation of the JNI functions, but their addresses will always be at predefined offsets. Through the JNIEnv argument, the programmer has access to a large set of functions. Instead, I'll show the rationale behind the use of these functions. For more detailed information, consult your compiler's JNI documentation. If you take a look at the jni.h header file, you'll see that inside the #ifdef __cplusplus preprocessor conditional, the JNIEnv_ structure is defined as a class when compiled by a C++ compiler. This class contains a number of inline functions that let you access the JNI functions with an easy and familiar syntax. In the rest of these examples, I'll use the C++ style. Accessing Java Strings As an example of accessing a JNI function, consider the code shown above. Here, the JNIEnv argument jEnv is used to access a Java String. Java Strings are in Unicode format, so if you receive one and want to pass it to a non-Unicode function (printf( ), for example), you must first convert it into ASCII characters with the JNI function GetStringUTFChars( ). This function takes a Java String and converts it to UTF-8 characters. To access the JNI function, we use the traditional C syntax for calling a function though a pointer. You use the form above to access all of the JNI functions. Passing and using Java objects In the previous example we passed a String to the native method. You can also pass Java objects of your own creation to a native method. Inside your native method, you can access the fields and methods of the object that was received. To pass objects, use the ordinary Java syntax when declaring the native method. In the example below, MyJavaClass has one public field and one public method. The class UseObjects declares a native method that takes an object of class MyJavaClass. To see if the native method manipulates its argument, the public field of the argument is set, the native method is called, and then the value of the public field is printed. In the example below, once the field and method ID are obtained, they are accessed through JNI functions. We simply read aValue, print it out, change the value, call the object's divByTwo( ) method, and print the value out again. To access a field or method, you must first obtain its identifier. Appropriate JNI functions take the class object, the element name, and the signature. These functions return an identifier that you use to access the element. This approach might seem convoluted, but your native method has no knowledge of the internal layout of the Java object. Instead, it must access fields and methods through indexes returned by the JVM. This allows different JVMs to implement different internal object layouts with no impact on your native methods. If you run the Java program, you'll see that the object that's passed from the Java side is manipulated by your native method. But what exactly is passed? A pointer or a Java reference? And what is the garbage collector doing during native method calls? The garbage collector continues to operate during native method execution, but it's guaranteed that your objects will not be garbage collected during a native method call. To ensure this, local references are created before, and destroyed right after, the native method call. Since their lifetime wraps the call, you know that the objects will be valid throughout the native method call. Since these references are created and subsequently destroyed every time the function is called, you cannot make local copies in your native methods, in static variables. If you want a reference that lasts across function invocations, you need a global reference. Global references are not created by the JVM, but the programmer can make a global reference out of a local one by calling specific JNI functions. When you create a global reference, you become responsible for the lifetime of the referenced object. The global reference (and the object it refers to) will be in memory until the programmer explicitly frees the reference with the appropriate JNI function. It's similar to malloc( ) and free( ) in C. JNI and Java exceptions With JNI, Java exceptions can be thrown, caught, printed, and rethrown just as they are inside a Java program. But it's up to the programmer to call dedicated JNI functions to deal with exceptions. Here are the JNI functions for exception handling: * Throw( ) Throws an existing exception object. Used in native methods to rethrow an exception. Does not return. Among these, you can't ignore ExceptionOccurred( ) and ExceptionClear( ). Most JNI functions can generate exceptions, and there is no language feature that you can use in place of a Java try block, so you must call ExceptionOccurred( ) after each JNI function call to see if an exception was thrown. If you detect an exception, you may choose to handle it (and possibly rethrow it). You must make certain, however, that the exception is eventually cleared. This can be done in your function using ExceptionClear( ) or in some other function if the exception is rethrown, but it must be done. You must ensure that the exception is cleared, because otherwise the results will be unpredictable if you call a JNI function while an exception is pending. There are few JNI functions that are safe to call during an exception; among these, of course, are all the exception handling functions. JNI and threading Since Java is a multithreaded language, several threads can call a native method concurrently. Basically, you have two options: declare the native method as synchronized or implement some other strategy within the native method to ensure correct, concurrent data manipulation. Also, you should never pass the JNIEnv pointer across threads, since the internal structure it points to is allocated on a per-thread basis and contains information that makes sense only in that particular thread. Using a pre-existing code base The easiest way to implement JNI native methods is to start writing native method prototypes in a Java class, compile that class, and run the.class file through javah. But what if you have a large, pre-existing code base that you want to call from Java? Renaming all the functions in your DLLs to match the JNI name mangling convention is not a viable solution. The best approach is to write a wrapper DLL outside your original code base. The Java code calls functions in this new DLL, which in turn calls your original DLL functions. This solution is not just a work-around; in most cases you must do this anyway because you must call JNI functions on the object references before you can use them. The Microsoft way At the time of this writing, Microsoft does not support JNI, but provides proprietary support to call non-Java code. This support is built into the compiler, the Microsoft JVM, and external tools. The features described in this section will work only if your program was compiled using the Microsoft Java compiler and run on the Microsoft Java Virtual Machine. If you plan to distribute your application on the Internet, or if your Intranet is built on different platforms, this can be a serious issue. 2. Raw Native Interface (RNI): You can call Win32 DLL functions, but you must then handle garbage collection. I'll cover all three techniques in the following sections. At the time of writing, these features were tested on the Microsoft SDK for Java 2.0 beta 2, which was downloaded (with a painful process they call Active Setup) from the Microsoft Web site. The Java SDK is a set of command-line tools, but the compilation engine can be easily plugged into the Developer Studio environment, allowing you to use Visual J++ 1.1 to compile Java 1.1 code. It was designed primarily to interface with the Win32 API, but you can use it to call any other APIs. The ease of use of this feature is counterbalanced by some limitations and reduced performance (compared to RNI). First, there is no need to write additional non-Java code, except the code in the DLL you want to call. Second, function arguments are automatically converted to and from standard data types. In just a few lines, this example calls the Win32 API function MessageBox( ), which pops up a little modal window with a title, a message, an optional icon, and a few buttons. The key is the @dll.import directive before the MessageBox( ) declaration, at the bottom of the example code. It looks like a comment, but it's not: it tells the compiler that the function below the directive is implemented in the USER32 DLL, and should be called accordingly. All you must do is supply a prototype that matches the function implementation in the DLL and call the function. But instead of typing in the Java version of each Win32 API function that you need, a Microsoft Java package does this for you (I'll describe this shortly). For this example to work, the function must be exported by name by the DLL, but the @dll.import directive can be used to link by ordinal as well, i.e., you can specify the entry position of the function in the DLL. I'll cover the features of the @dll.import directive later. An important issue in the process of linking with non-Java code is the automatic marshaling of the function parameters. As you can see, the Java declaration of MessageBox( ) takes two String arguments, but the original C implementation takes two char pointers. The compiler automatically converts the standard data types for you, following the rules described in a later section. Finally, you might have noticed the UnsatisfiedLinkError exception in the declaration of main( ). This exception occurs when the linker is unable to resolve the symbol for the non-Java function at run-time. For the DLL to be found, it must be in the Windows or Windows\System directory, in one of the directories listed in your PATH environment variable, or in the directory where the .class file is located. To get the compiler version number, run JVC from the command line with no parameters. To get the JVM version number, locate the icon for msjava.dll, and using the context menu look at its properties. It has a number of modifiers that you can use to customize the way you link to the non-Java code. It can also be applied to some methods within a class or to a whole class, meaning that all of the methods you declare in that class are implemented in the same DLL. Let's look at these features. Aliasing and linking by ordinal For the @dll.import directive to work as shown above, the function in the DLL must be exported by name. However, you might want to use a different name than the original one in the DLL (aliasing), or the function might be exported by number (i.e. by ordinal) instead of by name. The example below declares FinestraDiMessaggio( ) (the Italian equivalent of MessageBox) as an alias to MessageBox( ). As you can see, the syntax is pretty simple. The example assumes that there is a DLL named MYMATH somewhere along your path, and that this DLL contains at position 3 a function that takes two integers and gives you back the sum. Applying @dll.import to the entire class The @dll.import directive can be applied to an entire class, meaning that all of the methods in that class are implemented in the same DLL and with the same linkage attributes. You might think that you must use the approach above to map all of the Win32 API (functions, constants, and data types) to Java classes. Fortunately, you don't have to. The com.ms.win32 package The Win32 API is fairly big - on the order of a thousand functions, constants, and data types. Of course, you do not want to write the Java equivalent of every single Win32 API function. This package, named com.ms.win32, is installed in your classpath during the installation of the Java SDK 2.0 if you select it in the setup options. The package is made up of large number of Java classes that reproduce the constants, data structures, and functions of the Win32 API. The three richest classes are User32.class, Kernel32.class, and Gdi32.class. These contain the core of the Win32 API. To use them, just import them in your Java code. The MessageBeep( ) and MessageBox( ) functions can now be called with no other declarations. In MessageBeep( ) you can see that importing the package has also declared the Win32 constants. These constants are defined in a number of Java interfaces, all named winx (x is the first letter of the constant you want to use). At the time of this writing, the classes in the com.ms.win32 package are still under development, but usable nonetheless. In the example above, we called the MessageBox( ) function and passed it a couple of Strings. MessageBox( ) is a C function, and the binary layout of Java Strings is not the same as C strings, but the arguments are nonetheless correctly passed. This happens with all standard Java types. In most cases, you do not need to worry about converting to and from simple data types, but things are different when you must pass arguments of user-defined data types. For example, you might need to pass the address of a structured, user-defined data type, or you might need to pass a pointer to a raw memory area. For these situations, there are special compiler directives to mark a Java class so that it can be passed as a pointer to a structure (the @dll.struct directive). For details on the use of these keywords, please refer to the product documentation. Writing callback functions Some Win32 API functions require a function pointer as one of the parameters. The Windows API function may then call the argument function, possibly at a later time when some event occurs. This technique is called a callback function. Examples include window procedures and the callbacks you set up during a print operation (you give the print spooler the address of your callback function so it can update the status and possibly interrupt printing). Another example is the EnumWindows( ) API function that enumerates all top-level windows currently present in the system. EnumWindows( ) takes a function pointer, then traverses a list maintained internally by Windows. For every window in the list, it calls the callback function, passing the window handle as an argument to the callback. To do the same thing in Java, you must use the Callback class in the com.ms.dll package. You inherit from Callback and override callback( ). This method will accept only int parameters and will return int or void. The method signature and implementation depends on the Windows API function that's using this callback. Now all we need to do is create an instance of this Callback-derived class and pass it as the function pointer argument to the API function. The example below calls the EnumWindows( ) Win32 API; the callback( ) method in the EnumWindowsProc class gets the window handle for each top-level window, obtains the caption text, and prints it to the console window. The first is simplified access to OLE functions, and the second is the selection of the ANSI versus Unicode version of API functions. Here is a short description of the two. By convention, all OLE functions return a value of type HRESULT, which is a structured integer value defined by COM. If you program at the COM level and you want something different returned from an OLE function, you must pass it a pointer to a memory area that the function will fill with data. But in Java we don't have pointers; also, this style is not exactly elegant. A native method marked as an ole function is automatically translated from a Java-style method signature, which is where you decide the return type, into a COM-style function. The second feature selects between ANSI and Unicode string handling. Most Win32 API functions that handle strings come in two versions. For example, if you look at the symbols exported by the USER32 DLL, you will not find a MessageBox( ) function, but instead MessageBoxA( ) and MessageBoxW( ) functions, which are the ANSI and Unicode version, respectively. If you do not specify which version you want to call in the @dll.import directive, the JVM will try to figure it out. But this operation takes some time during program execution time that you can save with the ansi, unicode, or auto modifiers. For a more detailed discussion of these features, consult the Microsoft documentation. RNI is conceptually similar to Sun's JNI. Because of this, and because the product is not yet completed, I'll just point out the major differences. For further information, please refer to Microsoft's documentation. There are several notable differences between JNI and RNI. Below is the C header file generated by msjavah, the Microsoft equivalent of Sun's javah, applied to the ShowMsgBox Java class file used previously for the JNI example. In RNI, the native method programmer knows the binary layout of the objects. This allows you to directly access the information you want; you don't need to get a field or method identifier as in JNI. But since not all virtual machines necessarily use the same binary layout for their objects, the native method above is guaranteed to run only under the Microsoft JVM. In JNI, the JNIEnv argument gives access to a large number of functions to interact with the JVM. In RNI, the functions for controlling JVM operations are directly available. Some of them, like the one for handling exceptions, are similar to their JNI counterparts, but most of the RNI functions have different names and purposes from those in JNI. One of the most remarkable differences between JNI and RNI is the garbage collection model. In JNI, the GC basically follows the same rules during native method execution that it follows for the Java code execution. In RNI, the programmer is responsible for starting and stopping the Garbage Collector during native method activity. By default, the GC is disabled upon entering the native method; doing so, the programmer can assume that the objects being used will not be garbage collected during that time. But if the native method, let's say, is going to take a long time, the programmer is free to enable the GC, calling the GCEnable( ) RNI function. There is also something similar to the global handles features - something the programmer can use to be sure that specific objects will not be garbage collected when the CG is enabled. The concept is similar but the name is different: in RNI these guys are called GCFrames. RNI Summary The fact that RNI is tightly integrated with the Microsoft JVM is both its strength and its weakness. RNI is more complex than JNI, but it also gives you a high degree of control of the internal activities of the JVM, including garbage collection. Also, it is clearly designed for speed, adopting compromises and techniques that C programmers are familiar with. But it's not suitable for JVMs other than Microsoft's. These include ActiveX Controls, Automation, and ActiveX Documents. But COM is much more; it's a specification (and a partial implementation) for developing component objects that can interoperate using dedicated features of the operating system. In practice, all of the new software developed for Win32 systems has some relationship with COM - the operating system exposes some of its features via COM objects. Third-party components can be COM, and you can create and register your own COM components. In one way or another, if you want to write Win32 code, you'll have to deal with COM. This section kept things simple; the tools are actually much more powerful, and you can use them in a more sophisticated way. But this requires a deep knowledge of COM, which is beyond the scope of this appendix. For more information, Dale Rogerson's Inside COM (Microsoft Press, 1997) is an excellent book. Since COM is the architectural heart of all the new Win32 applications, being able to use, or to expose, COM services from Java code can be important. Java and COM are so similar in their models that the integration is conceptually straightforward and technically seamless - there's almost no special code to write in order to access COM. The effect is that the COM objects are seen as ordinary Java objects by the Java programmer, and COM clients can use COM servers implemented in Java just like any other COM server. Again, I use the generic term COM, but by extension this means that you can implement an ActiveX Automation server in Java, or you can use an ActiveX Control in your Java programs. The most notable similarities between Java and COM revolve around the relationship between COM interfaces and the Java interface keyword. This is a near-perfect match because: * A COM object exposes interfaces (and only interfaces). A Java class can implement an arbitrary number of Java interfaces * COM has a reference object model; the programmer never has an object, just references to one or more of its interfaces. Java has a reference object model as well - a reference to an object can be cast to a reference to one of its interfaces. In Java, the lifetime of an object is also determined by the number of clients. When there are no more references to that object, the object is a candidate to be released by the garbage collector. This tight mapping between Java and COM not only allows the Java programmer to easily access COM features, but it also makes Java an interesting language for writing COM code. COM is language-independent, but the de facto languages for COM development are C++ and Visual Basic. Compared to Java, C++ is much more powerful for COM development and generates much more efficient code, but it's hard to use. Visual Basic is much easier than Java, but it's too far from the underlying operating system, and its object model does not map very well to COM. Java is an excellent compromise between the two. COM Fundamentals COM is a binary specification for implementing interoperable objects. For example, COM describes the binary layout an object should have to be able to call services in another COM object. Since it's a description of a binary layout, COM objects can be implemented in any language that's able to produce such a layout. Usually the programmer is freed from these low level details, since the compiler takes care of generating the correct layout. For example, if you program in C++, most compilers generate a virtual function table that is COM-compliant. With languages that do not produce executable code, such as VB and Java, the runtime takes care of hooking into COM. The COM Library also supplies a few basic functions, such as the ones for creating an object or locating a COM class registered in your system. The main goals of a component object model are: * Let objects call services into other objects. The first point is exactly what object-oriented programming is about: you have a client object that makes requests to a server object. In this case, the terms client and server are used in a generic way, and not to refer to some particular hardware configuration. With any object-oriented language, the first goal is easy to achieve if your application is a monolithic piece of code that implements both the server object code and the client object code. If you make changes to the way client and the server objects interface with each other, you simply compile and link again. When you restart your application, it uses a new version of the components. The situation is completely different when your application is made up of component objects that are not under your control - you don't control their source code and they can evolve separately from your application. This is exactly the case, for example, when you use a third-party ActiveX Control in your application. The control is installed in your system, and your application is able, at runtime, to locate the server code, activate the object, link to it, and use it. Later, you can install a newer version of the control, and your application should still be able to run; in the worst case, it should gracefully report an error condition, such as Control not found, without hanging up. In these scenarios, your components are implemented in separate executable code files: DLLs or EXEs. If the server object is implemented in a separate executable code file, you need a standard, operating system supplied method to activate these objects. Of course, in your code you do not want to use the physical name and location of the DLL or EXE, because these might change; you want some identifier maintained by the operating system. Also, your application needs a description of the services exposed by the server. This is exactly what I'll cover in the next two sections. GUIDs and the Registry COM uses structured integer numbers, 128 bits long, to unequivocally identify COM entities registered in the system. These numbers, called GUIDs (Globally Unique IDentifiers) can be generated by specific utilities, and are guaranteed to be unique in space and in time, to quote Kraig Brockschmidt. In space, because the number is generator reads the id of your network card, and in time because the system date and time are used as well. A GUID can be used to identify a COM class (in which case it's called a CLSID) or a COM interface (IID). The names are different but the concept and the binary structure are the same. GUIDs are also used in other situations that I will not cover here. GUIDs, along with their associated information, are stored in the Windows Registry, or Registration Database. It's a hierarchical database, built into the operating system, which holds a great amount of information about the hardware and software configuration of your system. For COM, the Registry keeps track of the components installed in your system, such as their CLSIDs, the name and location of the executable file that implement them, and a lot of other details. One of these details is the ProgID of the component; a ProgID is conceptually similar to a GUID in the sense that it identifies a COM component. The difference is that a GUID is a binary, algorithmically-generated value, whereas a ProgID is a programmer-defined string value. A ProgID is associated with a CLSID. A COM component is said to be registered in the system when at least its CLSID and its executable file location are present in the Registry (the ProgID is usually present as well). Registering and using COM components is exactly what we'll do in the following examples. One of the effects of the Registry is as a decoupling layer between the client and server objects. The client activates the server using some information that is stored in the Registry; one piece of information is the physical location of the server executables. If the location changes, the information in the Registry is updated accordingly, but this is transparent to the client, which just uses ProgIDs or CLSIDs. In other words, the Registry allows for location transparency of the server code. These services are described in a binary, language-independent way (as interfaces and method signatures) in the type library. This can be a separate file (usually with the.TLB extension), or a Win32 resource linked into the executable. At runtime, the client uses the information in the type library to call functions in the server. You can generate a type library by writing a Microsoft Interface Definition Language (MIDL) source file and compiling it with the MIDL compiler to generate a.TLB file. MIDL is a language that describes COM classes, interfaces, and methods. The Java programmer has no need to use MIDL, though. A different Microsoft tool, described later, reads a Java class file and generates a type library. Function return codes in COM: HRESULT COM functions exposed by a server return a value of the predefined type HRESULT. An HRESULT is an integer containing three fields. This allows for multiple failure and success codes, along with additional information. Because a COM function returns an HRESULT, you cannot use the return value to hand back ordinary data from the function call. If you must return data, you pass a pointer to a memory area that the function will fill. This is known as an out parameter. This is described in the following sections. The compiler has special directives and packages for treating Java classes as COM classes, but in most cases, you'll just rely on the Microsoft JVM support for COM, and on a couple of external tools. The Microsoft Java Virtual Machine acts as a bridge between COM and Java objects. If you create a Java object as a COM server, your object will still be running inside the JVM. The Microsoft JVM is implemented as a DLL, which exposes COM interfaces to the operating system. Internally, the JVM maps function calls to these COM interfaces to method calls in your Java objects. Javareg reads a Java class file, generates a corresponding type library and a GUID, and registers the class in the system. Javareg can be used to register remote servers as well, for example, servers that run on a different physical machine. Again, the virtual machine interfaces with the COM server and exposes its services as methods in a Java class. Another Microsoft tool, jactivex, reads a type library and generates Java source files that contain special compiler directives. The generated source files are part of a package named after the type library you specified. The next step is to import that package in your COM client Java source files. Let's look at a couple of examples. Developing COM servers in Java This section shows the process you will apply to the development of ActiveX Controls, Automation Servers, or any other COM-compliant server. The following example implements a simple Automation server that adds integer numbers. You set the value of the addend with the setAddend( ) method, and each time you call the sum( ) method the addend is added to the current result. You retrieve the result with getResult( ) and reset the values with clear( ). Since no CLSID is given, Javareg will generate one; if we call Javareg again on the same server, the existing CLSID will be used. At this point, you must remember to copy your Adder.class file into the Windows\Java\trustlib directory. For security reasons, related mostly to the use of COM services by applets, your COM server will be activated only if it is installed in the trustlib directory. You now have a new Automation server installed on your system. To test it, you need an Automation controller, and the Automation Controller is Visual Basic (VB). Below, you can see a few lines of VB code. On the VB form, I put a text box to input the value of the addend, a label to show the result, and two push buttons to invoke the sum( ) and clear( ) methods. At the beginning, an object variable named Adder is declared. In the Form_Load subroutine, executed when the form is first displayed, a new instance of the Adder automation server is instantiated and the text fields on the form are initialized. When the user presses the Sum or Clear buttons, appropriate methods in the server are invoked. When you run this program and the CreateObject( ) function is called, the Windows Registry is searched for the specified ProgID. Among the information related to the ProgID is the name of the Java class file, so in response the Java Virtual Machine is started, and the Java object instantiated inside the JVM. From then on, the JVM takes care of the interaction between the client and server code. Developing COM clients in Java Now let's jump to the other side and develop a COM client in Java. This program will call services in a COM server that's installed on your system. The example is a client for the server we implemented in the previous example. While the code will look familiar to a Java programmer, what happens behind the scenes is quite unusual. This example uses a server that happens to be written in Java but applies to any ActiveX Control, ActiveX Automation server, or ActiveX component installed in your system for which you have a type library. First, the Jactivex tool is applied to the server's type library. In the example line below, it is applied to the type library that was generated for out COM Automation server. This is the Java equivalent of the type library. These files use compiler directives specific to the Microsoft compiler: the @com directives. The file named Adder.java is the equivalent of a coclass directive in a MIDL file: it's the declaration of a COM class. The other files are the Java equivalent of the COM interfaces exposed by the server. These interfaces, such as Adder_DispatchDefault.java, are dispatch interfaces, part of the mechanism of interaction between an Automation controller and an Automation server. IDispatch and dual interfaces are beyond the scope of this appendix. Below, you can see the client code. The first line just imports the package generated by jactivex. Then an instance of the COM Automation server is created and used, as if it was an ordinary Java class. Notice the typecast on the line where the COM object is instantiated. This is consistent with the COM object model. In COM, the programmer never has a reference to the whole object; instead, the programmer can only have references to one or more of the interfaces implemented in the class. Instantiating a Java object of the Adder class tells COM to activate the server and to create an instance of this COM object. But then we must specify which interface we want to use, choosing among the ones implemented by the server. This is exactly what the typecast does. The com.ms.com package The com.ms.com package defines a number of classes for COM development. It supports the use of GUIDs - the Variant and SafeArray Automation types - interfacing with ActiveX Controls at a deeper level and handling COM exceptions. I cannot cover all of these topics here, but I want to point out something about COM exceptions. By convention, virtually all COM functions return an HRESULT value that tells you if the function invocation succeeded or not and why. But if you look at the Java method signature in our server and client code, there no HRESULT. Instead, we use the function return value to get data back from some functions. The virtual machine is translating Java-style function calls into COM-style function calls, even for the return parameter. But what happens inside the virtual machine if one of the functions you call in the server fails at the COM level? In this case, the JVM sees that the HRESULT value indicates a failure and generates a native Java exception of class com.ms.com.ComFailException. In this way, you can handle COM errors using Java exception handling instead of checking function return values. To learn more about the classes in this package, please refer to the Microsoft documentation. The Microsoft JVM takes care of the details. An ActiveX Control is just a COM server exposing predefined, required interfaces. A Bean is just a Java class that is compliant with a specific programming style. At the time this was written, however, the integration was not perfect. For example, the virtual machine is not able to map the JavaBeans event model to the COM event model. If you want to handle events from a Bean inside an ActiveX container, the Bean must intercept system events such as mouse actions via low-level techniques, not the standard JavaBeans delegation event model. The concept and tools are exactly the same as discussed above, so please consult Microsoft's documentation for more details. A note about native methods and applets Native methods face the security issue. When your Java code calls a native method, you pass control outside of the virtual machine sandbox. The native method has complete access to the operating system. Of course, this is exactly what you want if you write native methods, but it is not acceptable for applets, at least not implicitly. You don't want an applet, downloaded from a remote Internet server, to be free to play with the file system and other critical areas of your machine unless you allow it to do so. Different conditions must be met depending on the feature the applet is trying to use. At the time of this writing, not all of these security mechanisms are implemented (in the Microsoft SDK for Java, beta 2), so keep an eye on the documentation as new versions become available. CORBA In large, distributed applications, your needs might not be satisfied by the preceding approaches. For example, you might want to interface with legacy datastores, or you might need services from a server object regardless of its physical location. These situations require some form of Remote Procedure Call (RPC), and possibly language independence. This is where CORBA can help. CORBA is not a language feature; it's an integration technology. It's a specification that vendors can follow to implement CORBA-compliant integration products. CORBA is part of the OMG's effort to define a standard framework for distributed, language-independent object interoperability. CORBA supplies the ability to make remote procedure calls into Java objects and non-Java objects, and to interface with legacy systems in a location-transparent way. Java adds networking support and a nice object-oriented language for building graphical and non-graphical applications. The Java and OMG object model map nicely to each other; for example, both Java and CORBA implement the interface concept and a reference object model. CORBA Fundamentals The object interoperability specification developed by the OMG is commonly referred to as the Object Management Architecture (OMA). The OMA defines two components: the Core Object Model and the OMA Reference Architecture. The Core Object Model states the basic concepts of object, interface, operation, and so on. The OMA Reference Architecture includes the Object Request Broker (ORB), Object Services (also known as CORBAservices), and common facilities. The ORB is the communication bus by which objects can request services from other objects, regardless of their physical location. This means that what looks like a method call in the client code is actually a complex operation. First, a connection with the server object must exist, and to create a connection the ORB must know where the server implementation code resides. Once the connection is established, the method arguments must be marshaled, i.e. converted in a binary stream to be sent across a network. Other information that must be sent are the server machine name, the server process, and the identity of the server object inside that process. Finally, this information is sent through a low-level wire protocol, the information is decoded on the server side, and the call is executed. The ORB hides all of this complexity from the programmer and makes the operation almost as simple as calling a method on local object. There is no specification for how an ORB Core should be implemented, but to provide a basic compatibility among different vendors' ORBs, the OMG defines a set of services that are accessible through standard interfaces. CORBA Interface Definition Language (IDL) CORBA is designed for language transparency: a client object can call methods on a server object of different class, regardless of the language they are implemented with. Of course, the client object must know the names and signatures of methods that the server object exposes. This is where IDL comes in. The CORBA IDL is a language-neutral way to specify data types, attributes, operations, interfaces, and more. The IDL syntax is similar to the C++ or Java syntax. The IDL compiler is an extremely useful tool: it doesn't just generate a Java source equivalent of the IDL, it also generates the code that will be used to marshal method arguments and to make remote calls. This code, called the stub and skeleton code, is organized in multiple Java source files and is usually part of the same Java package. The naming service The naming service is one of the fundamental CORBA services. A CORBA object is accessed through a reference, a piece of information that's not meaningful for the human reader. But references can be assigned programmer-defined, string names. This operation is known as stringifying the reference, and one of the OMA components, the Naming Service, is devoted to performing string-to-object and object-to-string conversion and mapping. Since the Naming Service acts as a telephone directory that both servers and clients can consult and manipulate, it runs as a separate process. Creating an object-to-string mapping is called binding an object, and removing the mapping is called unbinding. Getting an object reference passing a string is called resolving the name. For example, on startup, a server application could create a server object, bind the object into the name service, and then wait for clients to make requests. A client first obtains a server object reference, resolving the string name, and then can make calls into the server using the reference. Again, the Naming Service specification is part of CORBA, but the application that implements it is provided by the ORB vendor. The way you get access to the Naming Service functionality can vary from vendor to vendor. An example The code shown here will not be elaborate because different ORBs have different ways to access CORBA services, so examples are vendor specific. We want to implement a server, running on some machine, that can be queried for the exact time. We also want to implement a client that asks for the exact time. In this case we'll be implementing both programs in Java, but we could also use two different languages (which often happens in real situations). Writing the IDL source The first step is to write an IDL description of the services provided. This is usually done by the server programmer, who is then free to implement the server in any language in which a CORBA IDL compiler exists. The IDL file is distributed to the client side programmer and becomes the bridge between languages. The interface is made up of one single method the gives back the current time in string format. Creating stubs and skeletons The second step is to compile the IDL to create the Java stub and skeleton code that we'll use for implementing the client and the server. The tool that comes with the JavaIDL product is idltojava: idltojava -fserver -fclient RemoteTime.idl The two flags tell idltojava to generate code for both the stub and the skeleton. Idltojava generates a Java package named after the IDL module, RemoteTime, and the generated Java files are put in the RemoteTime subdirectory. There are Java representations of the IDL interface in ExactTime.java and a couple of other support files used, for example, to facilitate access to the naming service operations. Implementing the server and the client Below you can see the code for the server side. The server object implementation is in the ExactTimeServer class. The RemoteTimeServer is the application that creates a server object, registers it with the ORB, gives a name to the object reference, and then sits quietly waiting for client requests. Things get a bit more complicated when it comes to interacting with the ORB and other CORBA services. Some CORBA services This is a short description of what the JavaIDL-related code is doing (primarily ignoring the part of the CORBA code that is vendor dependent). The first line in main( ) starts up the ORB, and of course, this is because our server object will need to interact with it. Right after the ORB initialization, a server object is created. Actually, the right term would be a transient servant object: an object that receives requests from clients, and whose lifetime is the same as the process that creates it. Once the transient servant object is created, it is registered with the ORB, which means that the ORB knows of its existence and can now forward requests to it. Up to this point, all we have is timeServerObjRef, an object reference that is known only inside the current server process. The next step will be to assign a stringified name to this servant object; clients will use that name to locate the servant object. We accomplish this operation using the Naming Service. First, we need an object reference to the Naming Service; the call to resolve_initial_references( ) takes the stringified object reference of the Naming Service that is NameService, in JavaIDL, and returns an object reference. This is cast to a specific NamingContext reference using the narrow( ) method. We can use now the naming services. To bind the servant object with a stringified object reference, we first create a NameComponent object, initialized with ExactTime, the name string we want to bind to the servant object. Then, using the rebind( ) method, the stringified reference is bound to the object reference. We use rebind( ) to assign a reference, even if it already exists, whereas bind( ) raises an exception if the reference already exists. A name is made up in CORBA by a sequence of NameContexts - that's why we use an array to bind the name to the object reference. The servant object is finally ready for use by clients. At this point, the server process enters a wait state. Again, this is because it is a transient servant, so its lifetime is confined to the server process. JavaIDL does not currently support persistent objects - objects that survive the execution of the process that creates them. Next, we need an object reference for the servant object, so we pass the stringified object reference to the resolve( ) method, and we cast the result into an ExactTime interface reference using the narrow( ) method. Finally, we call getTime( ). Activating the name service process Finally we have a server and a client application ready to interoperate. You've seen that both need the naming service to bind and resolve stringified object references. You must start the naming service process before running either the server or the client. In JavaIDL, the naming service is a Java application that comes with the product package, but it can be different with other products. The JavaIDL naming service runs inside an instance of the JVM and listens by default to network port 900. Activating the server and the client Now you are ready to start your server and client application (in this order, since our server is transient). If everything is set up correctly, what you'll get is a single output line on the client console window, giving you the current time. This is a simple example, designed to work without a network, but an ORB is usually configured for location transparency. When the server and the client are on different machines, the ORB can resolve remote stringified references using a component known as the Implementation Repository. Although the Implementation Repository is part of CORBA, there is almost no specification, so it differs from vendor to vendor. As you can see, there is much more to CORBA than what has been covered here, but you should get the basic idea. There you'll find documentation, white papers, proceedings, and references to other CORBA sources and products. Java Applets and CORBA Java applets can act as CORBA clients. This way, an applet can access remote information and services exposed as CORBA objects. But an applet can connect only with the server from which it was downloaded, so all the CORBA objects the applet interacts with must be on that server. This is the opposite of what CORBA tries to do: give you complete location transparency. This is an issue of network security. If you're on an Intranet, one solution is to loosen the security restrictions on the browser. Or, set up a firewall policy for connecting with external servers. Some Java ORB products offer proprietary solutions to this problem. For example, some implement what is called HTTP Tunneling, while others have their special firewall features. This is too complex a topic to be covered in an appendix, but it is definitely something you should be aware of. CORBA vs. RMI You saw that one of the main CORBA features is RPC support, which allows your local objects to call methods in remote objects. Of course, there already is a native Java feature that does exactly the same thing: RMI (see Chapter 15). While RMI makes RPC possible between Java objects, CORBA makes RPC possible between objects implemented in any language. It's a big difference. However, RMI can be used to call services on remote, non-Java code. All you need is some kind of wrapper Java object around the non-Java code on the server side. This approach requires you to write a kind of integration layer, which is exactly what CORBA does for you, but then you don't need a third-party ORB. Summary What you've seen in this appendix are the most common techniques to call non-Java code from a Java application. Sun's JNI is flexible, reasonably simple (although it requires a lot of control over the JVM internals), powerful, and it's available on most JVMs, but not all. Finally, we took a look at CORBA, which allows your Java objects to talk to other objects regardless of their physical location and implementation language. CORBA is different from the techniques above because it is not integrated with the Java language, but instead uses third-party integration technology and requires that you buy a third-party ORB. CORBA is an interesting and general solution, but it might not be the best approach if you just want to make calls into the operating system. This makes sense since Java was derived from C++. However, there are a surprising number of differences between C++ and Java. These differences are intended to be significant improvements, and if you understand the differences you'll see why Java is such a beneficial programming language. This appendix takes you through the important features that distinguish Java from C++. 1. The biggest potential stumbling block is speed: interpreted Java runs in the range of 20 times slower than C. Nothing prevents the Java language from being compiled and there are just-in-time compilers appearing at this writing that offer significant speed-ups. It is not inconceivable that full native compilers will appear for the more popular platforms, but without those there are classes of problems that will be insoluble with Java because of the speed issue. 2. Java has both kinds of comments like C++ does. 3. Everything must be in a class. There are no global functions or global data. If you want the equivalent of globals, make static methods and static data within a class. There are no structs or enumerations or unions, only classes. 4. All method definitions are defined in the body of the class. Thus, in C++ it would look like all the functions are inlined, but they're not (inlines are noted later). 5. Class definitions are roughly the same form in Java as in C++, but there's no closing semicolon. There are no class declarations of the form class foo, only class definitions. Java uses the dot for everything, but can get away with it since you can define elements only within a class. Even the method definitions must always occur within a class, so there is no need for scope resolution there either. One place where you'll notice the difference is in the calling of static methods: you say ClassName.methodName( );. In addition, package names are established using the dot, and to perform a kind of C++ #include you use the import keyword. For example: import java.awt.*;. (#include does not directly map to import, but it has a similar feel to it). 7. Java, like C++, has primitive types for efficient access. In Java, these are boolean, char, byte, short, int, long, float, and double. All the primitive types have specified sizes that are machine independent for portability. For example: 1. Conditional expressions can be only boolean, not integral. 2. The result of an expression like X + Y must be used; you can't just say X + Y for the side effect. 8. The char type uses the international 16-bit Unicode character set, so it can automatically represent most national characters. 9. Static quoted strings are automatically converted into String objects. There is no independent static character array string like there is in C and C++. 10. All arrays are created on the heap, and you can assign one array to another (the array handle is simply copied). The array identifier is a first-class object, with all of the methods commonly available to all other objects. All objects of non-primitive types can be created only via new. There's no equivalent to creating non-primitive objects on the stack as in C++. All primitive types can be created only on the stack, without new. There are wrapper classes for all primitive classes so that you can create equivalent heap-based objects via new. No forward declarations are necessary in Java. If you want to use a class or a method before it is defined, you simply use it - the compiler ensures that the appropriate definition exists. Thus you don't have any of the forward referencing issues that you do in C++. 14. Java has no preprocessor. If you want to use classes in another library, you say import and the name of the library. There are no preprocessor-like macros. Java uses packages in place of namespaces. The name issue is taken care of by putting everything into a class and by using a facility called packages that performs the equivalent namespace breakup for class names. Packages also collect library components under a single library name. You simply import a package and the compiler takes care of the rest. Object handles defined as class members are automatically initialized to null. Initialization of primitive class data members is guaranteed in Java; if you don't explicitly initialize them they get a default value (a zero or equivalent). You can initialize them explicitly, either when you define them in the class or in the constructor. The syntax makes more sense than that for C++, and is consistent for static and non-static members alike. You don't need to externally define storage for static members like you do in C++. 17. There are no Java pointers in the sense of C and C++. When you create an object with new, you get back a reference (which I've been calling a handle in this book). For example: String s = new String(howdy); However, unlike C++ references that must be initialized when created and cannot be rebound to a different location, Java references don't have to be bound at the point of creation. They can also be rebound at will, which eliminates part of the need for pointers. The other reason for pointers in C and C++ is to be able to point at any place in memory whatsoever (which makes them unsafe, which is why Java doesn't support them). Pointers are often seen as an efficient way to move through an array of primitive variables; Java arrays allow you to do that in a safer fashion. The ultimate solution for pointer problems is native methods (discussed in Appendix A). Passing pointers to methods isn't a problem since there are no global functions, only classes, and you can pass references to objects. In any event, there's no pointer arithmetic. There are no destructors in Java. There is no scope of a variable per se, to indicate when the object's lifetime is ended - the lifetime of an object is determined instead by the garbage collector. If you need something done at a specific point, you must create a special method and call it, not rely upon finalize( ). Put another way, all objects in C++ will be (or rather, should be) destroyed, but not all objects in Java are garbage collected. Because Java doesn't support destructors, you must be careful to create a cleanup method if it's necessary and to explicitly call all the cleanup methods for the base class and member objects in your class. Java has method overloading that works virtually identically to C++ function overloading. Java does not support default arguments. There's no goto in Java. The one unconditional jump mechanism is the break label or continue label, which is used to jump out of the middle of multiply-nested loops. Java uses a singly-rooted hierarchy, so all objects are ultimately inherited from the root class Object. In C++, you can start a new inheritance tree anywhere, so you end up with a forest of trees. In Java you get a single ultimate hierarchy. This can seem restrictive, but it gives a great deal of power since you know that every object is guaranteed to have at least the Object interface. C++ appears to be the only OO language that does not impose a singly rooted hierarchy. Java has no templates or other implementation of parameterized types. The new collections in Java 1.2 are more complete, but still don't have the same kind of efficiency as template implementations would allow. Garbage collection means memory leaks are much harder to cause in Java, but not impossible. The garbage collector is a huge improvement over C++, and makes a lot of programming problems simply vanish. It might make Java unsuitable for solving a small subset of problems that cannot tolerate a garbage collector, but the advantage of a garbage collector seems to greatly outweigh this potential drawback. Java has built-in multithreading support. There's a Thread class that you inherit to create a new thread (you override the run( ) method). Mutual exclusion occurs at the level of objects using the synchronized keyword as a type qualifier for methods. Only one thread may use a synchronized method of a particular object at any one time. Put another way, when a synchronized method is entered, it first locks the object against any other synchronized method using that object and unlocks the object only upon exiting the method. There are no explicit locks; they happen automatically. You're still responsible for implementing more sophisticated synchronization between threads by creating your own monitor class. Recursive synchronized methods work correctly. Time slicing is not guaranteed between equal priority threads. Instead of controlling blocks of declarations like C++ does, the access specifiers (public, private, and protected) are placed on each definition for each member of a class. Without an explicit access specifier, an element defaults to friendly, which means that it is accessible to other elements in the same package (equivalent to them all being C++ friends) but inaccessible outside the package. The class, and each method within the class, has an access specifier to determine whether it's visible outside the file. Sometimes the private keyword is used less in Java because friendly access is often more useful than excluding access from other classes in the same package. Nested classes. In C++, nesting a class is an aid to name hiding and code organization (but C++ namespaces eliminate the need for name hiding). Java packaging provides the equivalence of namespaces, so that isn't an issue. Java 1.1 has inner classes that look just like nested classes. However, an object of an inner class secretly keeps a handle to the object of the outer class that was involved in the creation of the inner class object. This means that the inner class object may access members of the outer class object without qualification, as if those members belonged directly to the inner class object. This provides a much more elegant solution to the problem of callbacks, solved with pointers to members in C++. 29. Because of inner classes described in the previous point, there are no pointers to members in Java. No inline methods. The Java compiler might decide on its own to inline a method, but you don't have much control over this. You can suggest inlining in Java by using the final keyword for a method. However, inline functions are only suggestions to the C++ compiler as well. Inheritance in Java has the same effect as in C++, but the syntax is different. Java uses the extends keyword to indicate inheritance from a base class and the super keyword to specify methods to be called in the base class that have the same name as the method you're in. The base-class constructor is also called using the super keyword. As mentioned before, all classes are ultimately automatically inherited from Object. There's no explicit constructor initializer list like in C++, but the compiler forces you to perform all base-class initialization at the beginning of the constructor body and it won't let you perform these later in the body. Member initialization is guaranteed through a combination of automatic initialization and exceptions for uninitialized object handles. Inheritance in Java doesn't change the protection level of the members in the base class. You cannot specify public, private, or protected inheritance in Java, as you can in C++. Also, overridden methods in a derived class cannot reduce the access of the method in the base class. For example, if a method is public in the base class and you override it, your overridden method must also be public (the compiler checks for this). Java provides the interface keyword, which creates the equivalent of an abstract base class filled with abstract methods and with no data members. This makes a clear distinction between something designed to be just an interface and an extension of existing functionality via the extends keyword. It's worth noting that the abstract keyword produces a similar effect in that you can't create an object of that class. An abstract class may contain abstract methods (although it isn't required to contain any), but it is also able to contain implementations, so it is restricted to single inheritance. There's no virtual keyword in Java because all non-static methods always use dynamic binding. In Java, the programmer doesn't have to decide whether to use dynamic binding. The final keyword provides some latitude for efficiency tuning - it tells the compiler that this method cannot be overridden, and thus that it may be statically bound (and made inline, thus using the equivalent of a C++ non-virtual call). These optimizations are up to the compiler. Java doesn't provide multiple inheritance (MI), at least not in the same sense that C++ does. Like protected, MI seems like a good idea but you know you need it only when you are face to face with a certain design problem. Since Java uses a singly-rooted hierarchy, you'll probably run into fewer situations in which MI is necessary. The interface keyword takes care of combining multiple interfaces. The compiler automatically invokes the dynamic casting mechanism without requiring extra syntax. Although this doesn't have the benefit of easy location of casts as in C++ new casts, Java checks usage and throws exceptions so it won't allow bad casts like C++ does. Exception handling in Java is different because there are no destructors. A finally clause can be added to force execution of statements that perform necessary cleanup. All exceptions in Java are inherited from the base class Throwable, so you're guaranteed a common interface. Exception specifications in Java are vastly superior to those in C++. Instead of the C++ approach of calling a function at run-time when the wrong exception is thrown, Java exception specifications are checked and enforced at compile-time. In addition, overridden methods must conform to the exception specification of the base-class version of that method: they can throw the specified exceptions or exceptions derived from those. This provides much more robust exception-handling code. Java has method overloading, but no operator overloading. The String class does use the + and += operators to concatenate strings and String expressions use automatic type conversion, but that's a special built-in case. The const issues in C++ are avoided in Java by convention. You pass only handles to objects and local copies are never made for you automatically. If you want the equivalent of C++'s pass-by-value, you call clone( ) to produce a local copy of the argument (although the clone( ) mechanism is somewhat poorly designed - see Chapter 12). There's no copy-constructor that's automatically called. To create a compile-time constant value, you say, for example: static final int SIZE = 255; static final int BSIZE = 8 * SIZE; 41. Since Java can be too restrictive in some cases, you could be prevented from doing important tasks such as directly accessing hardware. Java solves this with native methods that allow you to call a function written in another language (currently only C and C++ are supported). Thus, you can always solve a platform-specific problem (in a relatively non-portable fashion, but then that code is isolated). Applets cannot call native methods, only applications. Java has built-in support for comment documentation, so the source code file can also contain its own documentation, which is stripped out and reformatted into HTML via a separate program. This is a boon for documentation maintenance and use. Java contains standard libraries for solving specific tasks. C++ relies on non-standard third-party libraries. Java 1.1 includes the Java Beans standard, which is a way to create components that can be used in visual programming environments. This promotes visual components that can be used under all vendor's development environments. Since you aren't tied to a particular vendor's design for visual components, this should result in greater selection and availability of components. In addition, the design for Java Beans is simpler for programmers to understand; vendor-specific component frameworks tend to involve a steeper learning curve. If the access to a Java handle fails, an exception is thrown. This test doesn't have to occur right before the use of a handle; the Java specification just says that the exception must somehow be thrown. Many C++ runtime systems can also throw exceptions for bad pointers. 1. Capitalize the first letter of class names. The first letter of fields, methods, and objects (handles) should be lowercase. All identifiers should run their words together, and capitalize the first letter of all intermediate words. For example: ThisIsAClassName thisIsAMethodOrFieldName Capitalize all the letters of static final primitive identifiers that have constant initializers in their definitions. This indicates they are compile-time constants. Packages are a special case: they are all lowercase letters, even for intermediate words. The domain extension (com, org, net, edu, etc.) should also be lowercase. 3. For each class you create, consider including a main( ) that contains code to test that class. You don't need to remove the test code to use the class in a project, and if you make any changes you can easily re-run the tests. This code also provides examples of how to use your class. 4. Methods should be kept to brief, functional units that describe and implement a discrete part of a class interface. Ideally, methods should be concise; if they are long you might want to search for a way to break them up into several shorter methods. This will also foster reuse within your class. 6. Try to keep classes small and focused. If you do, you'll wreck somebody's existing code, forcing them to rewrite and redesign. If you publicize only what you must, you can change everything else with impunity, and since designs tend to evolve this is an important freedom. Privacy is especially important when dealing with multithreading - only private fields can be protected against un-synchronized use. 8. Watch out for giant object syndrome. This is often an affliction of procedural programmers who are new to OOP and who end up writing a procedural program and sticking it inside one or two giant objects. With the exception of application frameworks, objects represent concepts in your application, not the application. 9. If you must do something ugly, at least localize the ugliness inside a class. Anytime you notice classes that appear to have high coupling with each other, consider the coding and maintenance improvements you might get by using inner classes (see Improving the code with an inner class on page 759). Use comments liberally, and use the javadoc comment-documentation syntax to produce your program documentation. Avoid using magic numbers, which are numbers hard-wired into code. These are a nightmare if you need to change them, since you never know if 100 means the array size or something else entirely. Instead, create a constant with a descriptive name and use the constant identifier throughout your program. This makes the program easier to understand and much easier to maintain. If your class requires any cleanup when the client programmer is finished with the object, place the cleanup code in a single, well- defined method with a name like cleanup( ) that clearly suggests its purpose. In addition, place a boolean flag in the class to indicate whether the object has been cleaned up. In the finalize( ) method for the class, check to make sure that the object has been cleaned up and throw a class derived from RuntimeException if it hasn't, to indicate a programming error. Before relying on such a scheme, ensure that finalize( ) works on your system. When overriding finalize( ) during inheritance, remember to call super.finalize( ) (this is not necessary if Object is your immediate superclass). You should call super.finalize( ) as the final act of your overridden finalize( ) rather than the first, to ensure that base-class components are still valid if you need them. When you are creating a fixed-size collection of objects, transfer them to an array (especially if you're returning this collection from a method). This way you get the benefit of the array's compile-time type checking, and the recipient of the array might not need to cast the objects in the array in order to use them. Choose interfaces over abstract classes. If you know something is going to be a base class, your first choice should be to make it an interface, and only if you're forced to have method definitions or member variables should you change it to an abstract class. An interface talks about what the client wants to do, while a class tends to focus on (or allow) implementation details. Inside constructors, do only what is necessary to set the object into the proper state. Actively avoid calling other methods (except for final methods) since those methods can be overridden by someone else to produce unexpected results during construction. Objects should not simply hold some data; they should also have well-defined behaviors. Choose composition first when creating new classes from existing classes. You should only used inheritance if it is required by your design. If you use inheritance where composition will work, your designs will become needlessly complicated. Use inheritance and method overriding to express differences in behavior, and fields to express variations in state. An extreme example of what not to do is inheriting different classes to represent colors instead of using a color field. To avoid a highly frustrating experience, make sure that there's only one class of each name anywhere in your classpath. Otherwise, the compiler can find the identically-named other class first, and report error messages that make no sense. If you suspect that you are having a classpath problem, try looking for.class files with the same names at each of the starting points in your classpath. When using the event adapters in the Java 1.1 AWT, there's a particularly easy pitfall you can encounter. If you override one of the adapter methods and you don't quite get the spelling right, you'll end up adding a new method rather than overriding an existing method. However, this is perfectly legal, so you won't get any error message from the compiler or run-time system - your code simply won't work correctly. Use design patterns to eliminate naked functionality. That is, if only one object of your class should be created, don't bolt ahead to the application and write a comment Make only one of these. Wrap it in a singleton. If you have a lot of messy code in your main program that creates your objects, look for a creational pattern like a factory method in which you can encapsulate that creation. Eliminating naked functionality will not only make your code much easier to understand and maintain, it will also make it more bulletproof against the well-intentioned maintainers that come after you. Watch out for premature optimization. First make it work, then make it fast - but only if you must, and only if it's proven that there is a performance bottleneck in a particular section of your code. Unless you have used a profiler to discover a bottleneck, you will probably be wasting your time. The hidden cost of performance tweaks is that your code becomes less understandable and maintainable. Remember that code is read much more than it is written. Clean designs make for easy-to-understand programs, but comments, detailed explanations, and examples are invaluable. They will help both you and everyone who comes after you. If nothing else, the frustration of trying to ferret out useful information from the online Java documentation should convince you. When you think you've got a good analysis, design, or implementation, do a walkthrough. Bring someone in from outside your group - this doesn't have to be a consultant, but can be someone from another group within your company. Reviewing your work with a pair of fresh eyes can reveal problems at a stage where it's much easier to fix them and more than pays for the time and money lost to the walkthrough process. Elegance always pays off. And there's nothing that matches the feeling that comes from knowing you've got an amazing design. Resist the urge to hurry; it will only slow you down. You can find other programming guidelines on the Web. The Java language emphasizes accurate, reliable behavior at the expense of performance. This is reflected in features such as automatic garbage collection, rigorous runtime checking, complete byte code checking, and conservative runtime synchronization. Availability on a wide choice of platforms leads, at present, to an interpreted virtual machine that further handicaps performance. If it meets your requirements, you are finished. If not, go to the next step. 2. Find the most critical performance bottleneck. This might require considerable ingenuity, but the effort will pay off. If you simply guess where the bottleneck is and try to optimize there, you'll waste your time. 3. Apply the speed improvement techniques discussed in this appendix, then return to Step 1. He changed a few lines in an hour of work and doubled the program speed. Working on the rest of the program would have dissipated his valuable time and effort. Since the compiler will ignore it when false, a static final boolean switch can turn the timing on and off so the code can efficiently be left in place in released code, ready for emergency use at any time. Even when more sophisticated profiling is available, this is a convenient way to time a specific task or operation. However, some systems with time resolution less than a millisecond (such as a Windows PC) need to repeat an operation n times and divide the total time by n to get accurate estimates. Unfortunately, the JDK profilers have uneven performance. JDK 1.1.1 works, but subsequent releases have had various instabilities. In fact, in JDK 1.0 it truncates the method names to 30 characters, so it might not be possible to distinguish between some methods. Tips for measuring performance * Since profiling uses clock time, make every effort to remove other processes during the measurement. Speedup techniques Now that the critical region has been isolated, you can apply two types of optimizations: generic techniques and those specific to Java. Generic approaches An effective generic speedup is to redefine the program in a more practical way. In addition, choosing a better algorithm will probably give a bigger performance gain than any other approach, particularly as the size of the data set increases. Language dependent approaches To put things in perspective, it's useful to look at the time it takes to perform various operations. So that the results are relatively independent of the computer being used, they have been normalized by dividing by the time it takes to make a local assignment. General modifications Here are some modifications that you can make to speed up time-critical parts of your Java program. Non-local or array loop variable Local loop variable Time (above) shows an instance integer assignment is 1.2 local integer assignments, but an array assignment is 2.7 local integer assignments. Linked list (fixed size) Saving discarded link items or replacing the list with a circular array (in which approximate size is known) Each new object takes 980 local assignments. The compiler can efficiently concatenate constant strings, but a variable string requires considerable processing. For example, if s and t are String variables: System.out.println(heading + s + trailer + t); this requires a new StringBuffer, appending arguments, and converting the result back to a String with toString( ). This costs both space and time. If you're appending more than one String, consider using a StringBuffer directly, especially if you can repeatedly reuse it in a loop. Preventing the creation of a new StringBuffer on each iteration saves the object creation time of 980 seen earlier. Using substring( ) and the other String methods is usually an improvement. When feasible, character arrays are even faster. Also notice that StringTokenizer is costly because of synchronization. Synchronization: In the JDK interpreter, calling a synchronized method is typically 10 times slower than calling an unsynchronized method. With JIT compilers, this performance gap has increased to 50 to 100 times (notice the timing above shows it to be 97 times slower). Avoid synchronized methods if you can - if you can't, synchronizing on methods rather than on code blocks is slightly faster. For example, rather than creating a new Font object in your paint( ) method, you can declare it an instance object, initialize it once, and then just update it when necessary in paint( ). See also Bentley, Programming Pearls p. When optimizing, combine small try-catch blocks, which thwart compiler optimization by breaking the code into small independent sections. On the other hand, be careful of sacrificing the robustness of your code by over-zealous removal of exception handling. Hashing: The standard Hashtable class in Java 1.0 and 1.1 requires casting and costly synchronization (570 assignment times). Furthermore, the early JDK library doesn't deliberately choose prime number table sizes. Finally, a hashing function should be designed for the particular characteristics of the keys actually used. For all these reasons, the generic Hashtable can be improved by designing a hash class that fits a particular application. Note that the HashMap in the Java 1.2 collections library has much greater flexibility and isn't automatically synchronized. Method inlining: Java compilers can inline a method only if it is final, private, or static, and in some cases it must have no local variables. If your code spends a lot of time calling a method that has none of these modifiers, consider writing a version that is final. Also notice that the Java 1.1 reader and writer classes were designed for improved performance. Casts and instanceof: Casts take from 2 to 200 assignment times. The more costly ones require travel up the inheritance hierarchy. Other costly operations lose and restore capabilities of the lower level constructs. Graphics: Use clipping to reduce the amount of work done in repaint( ), double buffering to improve perceived speed, and image strips or compression to speed downloading times. Animation in Java Applets from JavaWorld and Performing Animation from Sun are two good tutorials. Remember to use high-level primitives; it's much faster to call drawPolygon( ) on a bunch of points than looping with drawLine( ). If you must draw a one-pixel-wide line, drawLine(x,y,x,y) is faster than fillRect(x,y,1,1). Using API classes: Use classes from the Java API when they offer native machine performance that you can't match using Java. For example, arrayCopy( ) is much faster than using a loop to copy an array of any significant size. Replacing API classes: Sometimes API classes do more than you need, with a corresponding increase in execution time. For these you can write specialized versions that do less but run faster. For example, one application that needed a container to store many arrays was speeded by replacing the original Vector with a faster dynamic array of objects. Other suggestions * Move repeated constant calculations out of a critical loop, for example, computing buffer.length for a constant-size buffer. Note that your classes may get larger in size (JDK 1.1 or later only - earlier versions might not perform byte verification). Newer just-in-time (JIT) compilers will dramatically speed the code. 105-33. Nguyen, and Sandeep K. Singhal; IBM Software Solutions, IBM T.J. Watson Research Center. Van Wyk, Addison-Wesley, 1988. 110 and p. 145-151. Association for Computing Machinery, February 1988. Part II addresses generic performance enhancements. The definitive encyclopedia of algorithms. The author is an apprentice of Knuth's. This is one of seven editions devoted to several languages and contains timely, somewhat simpler treatments of algorithms. However, I've begun to see that many of my doubts about speed come from early implementations that were not particularly efficient so there was no model at which to point to explain how Java could be fast. Part of the way I've thought about speed has come from being cloistered with the C++ model. C++ is very focused on everything happening statically, at compile time, so that the run-time image of the program is small and fast. However, creating heap objects in C++ is typically much slower because it's based on the C concept of a heap as a big pool of memory that (and this is essential) must be recycled. Searching for available pieces of memory is the reason that allocating heap storage has such a performance impact in C++, so it's far faster to create stack-based objects. Again, because so much of C++ is based on doing everything at compile-time, this makes sense. But in Java there are certain places where things happen more dynamically and it changes the model. When it comes to creating objects, it turns out that the garbage collector can have a significant impact on increasing the speed of object creation. This real estate can become abandoned sometime later and must be reused. In some JVMs, the Java heap is quite different; it's more like a conveyor belt that moves forward every time you allocate a new object. This means that object storage allocation is remarkably rapid. The heap pointer is simply moved forward into virgin territory, so it's effectively the same as C++'s stack allocation. The garbage collector rearranges things and makes it possible for the high-speed, infinite-free-heap model to be used while allocating storage. To understand how this works, you need to get a little better idea of the way the different garbage collector (GC) schemes work. A simple but slow GC technique is reference counting. This means that each object contains a reference counter, and every time a handle is attached to an object the reference count is increased. Every time a handle goes out of scope or is set to null, the reference count is decreased. Thus, managing reference counts is a small but constant overhead that happens throughout the lifetime of your program. The garbage collector moves through the entire list of objects and when it finds one with a reference count of zero it releases that storage. The one drawback is that if objects circularly refer to each other they can have non-zero reference counts while still being garbage. Locating such self-referential groups requires significant extra work for the garbage collector. Reference counting is commonly used to explain one kind of garbage collection but it doesn't seem to be used in any JVM implementations. In faster schemes, garbage collection is not based on reference counting. Instead, it is based on the idea that any non-dead object must ultimately be traceable back to a handle that lives either on the stack or in static storage. The chain might go through several layers of objects. Thus, if you start in the stack and the static storage area and walk through all the handles you'll find all the live objects. Each object that you move through must still be alive. Note that there is no problem with detached self-referential groups - these are simply not found, and are therefore automatically garbage. In the approach described here, the JVM uses an adaptive garbage-collection scheme, and what it does with the live objects that it locates depends on the variant currently being used. One of these variants is stop-and-copy. This means that, for reasons that will become apparent, the program is first stopped (this is not a background collection scheme). Then, each live object that is found is copied from one heap to another, leaving behind all the garbage. In addition, as the objects are copied into the new heap they are packed end-to-end, thus compacting the new heap (and allowing new storage to simply be reeled off the end as previously described). Of course, when an object is moved from one place to another, all handles that point at (reference) that object must be changed. There are two issues that make copy collectors inefficient. The first is the idea that you have two heaps and you slosh all the memory back and forth between these two separate heaps, maintaining twice as much memory as you actually need. Some JVMs deal with this by allocating the heap in chunks as needed and simply copying from one chunk to another. The second issue is the copying. Once your program becomes stable it might be generating little or no garbage. Despite that, a copy collector will still copy all the memory from one place to another, which is wasteful. To prevent this, some JVMs detect that no new garbage is being generated and switch to a different scheme (this is the adaptive part). This other scheme is called mark and sweep, and it's what Sun's JVM uses all the time. For general use mark and sweep is fairly slow, but when you know you're generating little or no garbage it's fast. Mark and sweep follows the same logic of starting from the stack and static storage and tracing through all the handles to find live objects. However, each time it finds a live object that object is marked by setting a flag in it, but the object isn't collected yet. Only when the marking process is finished does the sweep occur. During the sweep, the dead objects are released. However, no copying happens, so if the collector chooses to compact a fragmented heap it does so by shuffling objects around. The stop-and-copy refers to the idea that this type of garbage collection is not done in the background; instead, the program is stopped while the GC occurs. In the Sun literature you'll find many references to garbage collection as a low-priority background process, but it turns out that this was a theoretical experiment that didn't work out. In practice, the Sun garbage collector is run when memory gets low. In addition, mark-and-sweep requires that the program be stopped. As previously mentioned, in the JVM described here memory is allocated in big blocks. If you allocate a large object, it gets its own block. Strict stop-and-copy requires copying every live object from the source heap to a new heap before you could free the old one, which translates to lots of memory. With blocks, the GC can typically use dead blocks to copy objects to as it collects. Each block has a generation count to keep track of whether it's alive. In the normal case, only the blocks created since the last GC are compacted; all other blocks get their generation count bumped if they have been referenced from somewhere. This handles the normal case of lots of short-lived temporary objects. Periodically, a full sweep is made - large objects are still not copied (just get their generation count bumped) and blocks containing small objects are copied and compacted. The JVM monitors the efficiency of GC and if it becomes a waste of time because all objects are long-lived then it switches to mark-and-sweep. Similarly, the JVM keeps track of how successful mark-and-sweep is, and if the heap starts to become fragmented it switches back to stop-and-copy. This is where the adaptive part comes in, so you end up with a mouthful: adaptive generational stop-and-copy mark-and-sweep. There are a number of additional speedups possible in a JVM. An especially important one involves the operation of the loader and Just-In-Time (JIT) compiler. When a class must be loaded (typically, the first time you want to create an object of that class), the.class file is located and the byte codes for that class are brought into memory. An alternative approach is lazy evaluation, which means that the code is not JIT compiled until necessary. Thus, code that never gets executed might never get JIT compiled. Because JVMs are external to browsers, you might expect that you could benefit from the speedups of some JVMs while using any browser. Unfortunately, JVMs don't currently interoperate with different browsers. To get the benefits of a particular JVM, you must either use the browser with that JVM built in or run standalone Java applications. 1997. A compact summary of the online documentation of Java 1.1. Personally, I prefer to browse the docs online, especially since they change so often. However, many folks still like printed documentation and this fits the bill; it also provides more discussion than the online documents. The Java Class Libraries: An Annotated Reference, by Patrick Chan and Rosanna Lee, Addison-Wesley 1997. What the online reference should have been: enough description to make it usable. One of the technical reviewers for Thinking in Java said, If I had only one Java book, this would be it (well, in addition to yours, of course). I'm not as thrilled with it as he is. It's big, it's expensive, and the quality of the examples doesn't satisfy me. But it's a place to look when you're stuck and it seems to have more depth (and sheer size) than Java in a Nutshell. Java Network Programming, by Elliote Rusty Harold, O'Reilly 1997. I didn't begin to understand Java networking until I found this book. I also find his Web site, Caf‚ au Lait, to be a stimulating, opinionated, and up-to-date perspective on Java developments, unencumbered by allegiances to any vendors. His almost daily updating keeps up with fast-changing news about Java. A good place to go for questions you can't find the answers to in Thinking in Java. Note: the Java 1.1 revision is Core Java 1.1 Volume 1 - Fundamentals and Core Java 1.1 Volume 2 - Advanced Features. JDBC Database Access with Java, by Hamilton, Cattell and Fisher (Addison-Wesley, 1997). If you know nothing about SQL and databases, this is a nice, gentle introduction. It also contains some of the details as well as an annotated reference to the API (again, what the online reference should have been). The drawback, as with all books in The Java Series (The ONLY Books Authorized by JavaSoft) is that it's been whitewashed so that it says only wonderful things about Java - you won't find out about any dark corners in this series. Java Programming with CORBA Andreas Vogel and Keith Duddy (John Wiley and Sons, 1997). A serious treatment of the subject with code examples for the three main Java ORBs (Visibroker, Orbix, Joe). Design Patterns, by Gamma, Helm, Johnson and Vlissides (Addison-Wesley 1995). The seminal book that started the patterns movement in programming. UML Tookit, by Hans-Erik Eriksson and Magnus Penker, (John Wiley and Sons, 1997). Explains UML and how to use it, and has a case study in Java. An accompanying CD-ROM contains the Java code and a cut-down version of Rational Rose. An excellent introduction to UML and how to use it to build a real system. Practical Algorithms for Programmers, by Binstock and Rex (Addison-Wesley 1995). The algorithms are in C, so they're fairly easy to translate into Java. Each algorithm is thoroughly explained. Following Java style, the capitalized names refer to Java classes, while lowercase names refer to a general concept. 2 See Multiparadigm Programming in Leda by Timothy Budd (Addison-Wesley 1995). 3 Some people make a distinction, stating that type determines the interface while class is a particular implementation of that interface. 4 I'm indebted to my friend Scott Meyers for this term. 5 This uses the Unified Notation, which will primarily be used in this book. 6 Note that this is true only for objects that are created on the heap, with new. However, the problem described, and indeed any general programming problem, requires objects to be created on the heap. 7 According to a technical reader for this book, one existing real-time Java implementation (www.newmonics.com) has guarantees on garbage collector performance. 8 The material in this section is adapted from an article by the author that originally appeared on Mainspring, at www.mainspring.com. Used with permission. 9 The best introduction is still Grady Booch's Object-Oriented Design with Applications, 2nd edition, Wiley and Sons 1996. His insights are clear and his prose is straightforward, although his notations are needlessly complex for most designs. The trouble with rapid prototyping is that people didn't throw away the prototype, but instead built upon it. Combined with the lack of structure in procedural programming, this often leads to messy systems that are expensive to maintain. 1 In Java version 1.1 only, not in 1.0. 2 In C++ you should often use the safer containers in the Standard Template Library as an alternative to arrays. 3 static methods, which you'll learn about soon, can be called for the class, without an object. 4 With the usual exception of the aforementioned special data types boolean, char, byte, short, int, long, float, and double. In general, though, you pass objects, which really means you pass handles to objects. 5 Some programming environments will flash programs up on the screen and close them before you've had a chance to see the results. This code involves concepts that will not be introduced until much later in the book, so you won't understand it until then, but it will do the trick. 1 John Kirkham writes, I started computing in 1962 using FORTRAN II on an IBM 1620. At that time, and throughout the 1960s and into the 1970s, FORTRAN was an all uppercase language. This probably started because many of the early input devices were old teletype units that used 5 bit Baudot code, which had no lowercase capability. The 'E' in the exponential notation was also always upper case and was never confused with the natural logarithm base 'e', which is always lower case. The 'E' simply stood for exponential, which was for the base of the number system used - usually 10. At the time octal was also widely used by programmers. Although I never saw it used, if I had seen an octal number in exponential notation I would have considered it to be base 8. The first time I remember seeing an exponential using a lower case 'e' was in the late 1970s and I also found it confusing. The problem arose as lowercase crept into FORTRAN, not at its beginning. 2 The one case in which this is possible occurs if you pass a handle to an object into the static method. Then, via the handle (which is now effectively this), you can call non-static methods and access non-static fields. But typically if you want to do something like this you'll just make an ordinary, non-static method. 3 Unfortunately, the implementations of the garbage collector in Java 1.0 would never call finalize( ) correctly. As a result, finalize( ) methods that were essential (such as those to close a file) often didn't get called. The documentation claimed that all finalizers would be called at the exit of a program, even if the garbage collector hadn't been run on those objects by the time the program terminated. This wasn't true, so as a result you couldn't reliably expect finalize( ) to be called for all objects. Effectively, finalize( ) was useless in Java 1.0. 4 By the time you read this, some Java Virtual Machines may show different behavior. 5 In contrast, C++ has the constructor initializer list that causes initialization to occur before entering the constructor body, and is enforced for objects. See Thinking in C++. 6 See Thinking in C++ for a complete description of aggregate initialization. 1 There's nothing in Java that forces the use of an interpreter. There exist native-code Java compilers that generate a single executable file. These will be introduced in Chapter 7. 5 You can also do it by inheriting (Chapter 6) from that class. 1 This approach was inspired by an e-mail from Rich Hoffarth. 2 This is very different from the design of nested classes in C++, which is simply a name-hiding mechanism. There is no link to an enclosing object and no implied permissions in C++. 3 On the other hand, '$' is a meta-character to the Unix shell and so you'll sometimes have trouble when listing the.class files. This is a bit strange coming from Sun, a Unix-based company. My guess is that they weren't considering this issue, but instead thought you'd naturally focus on the source-code files. 4 For some reason this has always been a pleasing problem for me to solve; it came from C++ Inside and Out, but Java allows a much more elegant solution. 1 This is one of the places where C++ is distinctly superior to Java, since C++ supports parameterized types with the template keyword. 2 The term iterator is common in C++ and elsewhere in OOP, so it's difficult to know why the Java team used a strange name. The collections library in Java 1.2 fixes this as well as many other problems. 3 If you plan to use RMI (described in Chapter 15), you should be aware that there's a problem when putting remote objects into a Hashtable. 4 If these speedups still don't meet your performance needs, you can further accelerate table lookup by writing your own hash table routine. This avoids delays due to casting to and from Objects and synchronization built into the Java Class Library hash table routine. 5 The best reference I know of is Practical Algorithms for Programmers, by Andrew Binstock and John Rex, Addison-Wesley 1995. 6 This chapter was written while Java 1.2 was still in beta, so the diagram does not show the TreeSet class that was added later. 7 At the time of this writing, TreeSet had only been announced and was not yet implemented, so there are no examples here that use TreeSet. 8 TreeSet was not available at the time of this writing, but you can easily add a test for it into this example. 9 At the time of this writing, a Collections.stableSort( ) had been announced, to perform a merge sort, but it was unavailable for testing. 1 The C programmer can look up the return value of printf( ) for an example of this. 2 This is a significant improvement over C++ exception handling, which doesn't catch violations of exception specifications until run time, when it's not very useful. This is one case in which C++ is actually able to check exception specifications at compile time. 4 C++ exception handling does not have the finally clause because it relies on destructors to accomplish this sort of cleanup. 5 A destructor is a function that's always called when an object becomes unused. You always know exactly where and when the destructor gets called. C++ has automatic destructor calls, but Delphi's Object Pascal versions 1 and 2 do not (which changes the meaning and use of the concept of a destructor for that language). 6 In C++, a destructor would handle this for you. 1 In Design Patterns, Erich Gamma et al., Addison-Wesley 1995. Described later in this book. 2 Perhaps by the time you read this, the bug will be fixed. 1 In C, which generally handles small bits of data, the default is pass-by-value. C++ had to follow this form, but with objects pass-by-value isn't usually the most efficient way. In addition, coding classes to support pass-by-value in C++ is a big headache. 2 This is not the dictionary spelling of the word, but it's what is used in the Java library, so I've used it here, too, in some hopes of reducing confusion. If you call it from a different class, it won't compile. 4 C++ allows the programmer to overload operators at will. Because this can often be a complicated process (see Chapter 10 of Thinking in C++ Prentice-Hall, 1995), the Java designers deemed it a bad feature that shouldn't be included in Java. 1 It is assumed that the reader is familiar with the basics of HTML. It's not too hard to figure out, and there are lots of books and resources. 3 ShowStatus( ) is also a method of Applet, so you can call it directly, without calling getAppletContext( ). 4 This behavior is apparently a bug and will be fixed in a later version of Java. 5 There is no MouseMotionEvent even though it seems like there ought to be. Clicking and motion is combined into MouseEvent, so this second appearance of MouseEvent in the table is not an error. 6 It also solves the problem of callbacks without adding any awkward method pointer feature to Java. 7 At the time this section was written, the Swing library had been pronounced frozen by Sun, so this code should compile and run without problems as long as you've downloaded and installed the Swing library. 8 This may also be a result of using pre-beta software. 1 The Java Programming Language, by Ken Arnold and James Gosling, Addison-Wesley 1996 pp 179. 1 This means a maximum of just over four billion numbers, which is rapidly running out. The new standard for IP addresses will use a 128-bit number, which should produce enough unique IP addresses for the foreseeable future. 2 TCP and UDP ports are considered unique. That is, you can simultaneously run a TCP and UDP server on port 8080 without interference. 3 You can test this under Windows32 using the Microsoft Personal Web Server that comes with Microsoft Office 97 and some of their other products. This is a nice way to experiment since you can perform local tests (and it's also fast). If you're on a different platform or if you don't have Office 97, you might be able to find a freeware Web server for testing by searching the Internet. 4 GNU stands for Gnu's Not Unix. The project, created by the Free Software Foundation, was originally intended to replace the Unix operating system with a free version of that OS. Linux appears to have replaced this initiative, but the GNU tools have played an integral part in the development of Linux, which comes packaged with many GNU components. 5 My book Thinking in C++ (Prentice-Hall, 1995) devotes an entire chapter to this subject. Refer to this if you need further information on the subject. 6 I can't say I really understand what's going on here, but I managed to get it working by studying Java Network Programming by Elliotte Rusty Harold (O'Reilly 1997). He alludes to a number of confusing bugs in the Java networking libraries, so this is an area in which you can't just write code and have it work right away. Be warned. 7 Many brain cells died in agony to discover this information.