Arizona s First University. Mastering Data Abstraction or Get nit-picky on design advantages Luke: Your not my father! Vader: You mean, You re not my father Luke: What? Vader: You used the possessive, not the contraction of `you are Luke: Uggh! You don t understand me! I ll be in my room!!!
Overview A little review Data vs. Abstraction Implementation details The Handle Why recompile? Object creation control Implementation alt s Temporary objects Examining cost Proxies Component construction
A little review We have been learning how to create the architecture of software Concepts have been examined Inheritance Friends Operators Generic Programming Concepts have been applied Mmmmmmm, homework
Data What is data?
Abstraction? And what, then, is abstraction?
Abstraction vs. Data Well, here s what I think Abstraction is making something into something that it isn t Data is something that is All models are wrong, some models are useful. - George Box Therefore, data abstraction is turning something into nothing that isn t anything, or something Seriously you guys, Data abstraction is the supreme control of data A protective shield over control of data
Data abstraction Let s consider: You are designing a software system You go to your customer s office Q: What are the first words out of your mouth? a) Hey you guys, let me show you the syntax for these operators! b) You are not gonna believe how I implemented this bubble sort c) I just finally understood what protected access regions do!
Data abstraction Why do your clients care? They don t They are programmers, and your software system is actually a header file they are including Data abstraction is for the programmer, not the end user All your end user wants is functionality Your job is to provide that functionality This third sentence contains the word functionality
Use cases Compiler Uses headers Uses implementation Customer Uses compiled implementation Side-car programmer Uses headers Links in implementation using the compiler
Does size matter? Whatsa matta here? // Author: Jonathan Sprinkle // size0.h #include <cstdio> #include <string> class John; class Sherlock; class John public:! John( ) }! ~John( ) } protected:! Sherlock crap; }; #include "size0.h" int main( void )! John j;! Sherlock s;! return 0; } class Sherlock public:! Sherlock( ) }! ~Sherlock( ) } protected:! John john; };
Wait for it...wait... for... It... Scanning dependencies of target lec22-size0 [ 98%] Building CXX object src/lectures/lec22/cmakefiles/lec22-size0.dir/size0.cpp.o In file included from /Users/sprinkle/work/teaching/ece373/src/lectures/lec22/size0.cpp:1: /Users/sprinkle/work/teaching/ece373/src/lectures/lec22/size0.h:15: error: field crap has incomplete type make[2]: *** [src/lectures/lec22/cmakefiles/lec22-size0.dir/size0.cpp.o] Error 1 make[1]: *** [src/lectures/lec22/cmakefiles/lec22-size0.dir/all] Error 2 make: *** [all] Error 2 * * roughly translated from the French, No Pipe, Sherlock
Can you hear me now? // Author: Jonathan Sprinkle // size_1.h #include <cstdio> #include <string> class John; class Sherlock; class John public:! John( ) }! ~John( ) } protected:! Sherlock* crap; }; #include "size1.h" int main( void )! John j;! Sherlock s;! return 0; } class Sherlock public:! Sherlock( ) }! ~Sherlock( ) } protected:! John john; };
Good! Scanning dependencies of target lec22-size1 [100%] Building CXX object src/lectures/lec22/cmakefiles/lec22-size1.dir/size1.cpp.o Linking CXX executable lec22-size1 [100%] Built target lec22-size1
gears.insert( *monkeywrench ) // Author: Jonathan Sprinkle // size2.h #include <cstdio> #include <string> class John; class Sherlock; class John public:! John( ) }! ~John( ) } protected:! Sherlock& crap; }; #include "size2.h" int main( void )! John j;! Sherlock s;! return 0; } class Sherlock public:! Sherlock( ) }! ~Sherlock( ) } protected:! John john; };
cookies->toss( all ) Scanning dependencies of target lec22-size2 [100%] Building CXX object src/lectures/lec22/cmakefiles/lec22-size2.dir/ size2.cpp.o In file included from /Users/sprinkle/work/teaching/ece373/src/lectures/lec22/ size2.cpp:1: /Users/sprinkle/work/teaching/ece373/src/lectures/lec22/size2.h: In constructor John::John() : /Users/sprinkle/work/teaching/ece373/src/lectures/lec22/size2.h:12: error: uninitialized reference member John::crap make[2]: *** [src/lectures/lec22/cmakefiles/lec22-size2.dir/size2.cpp.o] Error 1 make[1]: *** [src/lectures/lec22/cmakefiles/lec22-size2.dir/all] Error 2 make: *** [all] Error 2
And now... // Author: Jonathan Sprinkle // size3.h #include <cstdio> #include <string> class John; class Sherlock; class John public:! John( ) crap = new Sherlock( ); }! ~John( ) } protected:! Sherlock* crap; }; #include "size3.h" int main( void )! John j;! Sherlock s;! return 0; } class Sherlock public:! Sherlock( ) }! ~Sherlock( ) } protected:! John john; };
Out-Smurfed again... Scanning dependencies of target lec22-size3 [100%] Building CXX object src/lectures/lec22/cmakefiles/lec22-size3.dir/ size3.cpp.o In file included from /Users/sprinkle/work/teaching/ece373/src/lectures/lec22/ size3.cpp:1: /Users/sprinkle/work/teaching/ece373/src/lectures/lec22/size3.h: In constructor John::John() : /Users/sprinkle/work/teaching/ece373/src/lectures/lec22/size3.h:12: error: invalid use of incomplete type struct Sherlock /Users/sprinkle/work/teaching/ece373/src/lectures/lec22/size3.h:7: error: forward declaration of struct Sherlock make[2]: *** [src/lectures/lec22/cmakefiles/lec22-size3.dir/size3.cpp.o] Error 1 make[1]: *** [src/lectures/lec22/cmakefiles/lec22-size3.dir/all] Error 2 make: *** [all] Error 2
Why, why why? Why do we care about any of this? Can t we all just get along (and use pointers) to make our lives easier? What are the benefits/drawbacks of different types of containment? What effect will our decisions have on future versions of the tool?
Pointers and the easy life Pointers make life very nice They are always the same size (on the same machine) This will be very useful, as you will see, later They are always the same thing too (just a memory space) Pointers are so cool They do not require implementation to exist in a class (thus you can have classes contain each other) They oooze complexity you can show off to your friends at parties! Yeah!
Friends and Benefits Containment can be more complex than just pointers, though While it seems tempting to have all objects contained as pointers, you should consider carefully the ramifications of choosing a pointer over a value Some objects don t make sense as pointers (e.g., the int type) A little extra space is always taken up when you use a pointer (when you look globally at the system) Dereferencing all the frickin time can be expensive, depending on what your system is doing all the frickin time
Effects of our decisions When developing software, these words really suck: You must recompile your code to affect these changes Not only relink, but recompile Why would you ever need to recompile? Size of a class changes Interface to a class changes Won t the interface always change if the size does?
Size matters (again) A: well, not always // size_4.h #include <cstdio> #include <string> class John; class Sherlock; class John public: John( ) } ~John( ) } protected: Sherlock* crap; John* pilot2bomb; }; class Sherlock public: Sherlock( ) } ~Sherlock( ) } protected: John john; }; #include "size_4.h" int main( void ) John j; Sherlock s; return 0; } Sherlock s interface stayed exactly the same, surely I won t need to recompile it...
You will need to recompile... And stop calling me Shirley!
Stupid lousy compiler... // size_4.h #include <cstdio> #include <string> class John; class Sherlock; class John public: John( ) } ~John( ) } protected: Sherlock* crap; John* pilot2bomb; }; class Sherlock public: Sherlock( ) } ~Sherlock( ) } protected: John john; }; #include "size_4.h" int main( void ) John j; Sherlock s; return 0; } The compiler needs to know exactly how big to make the John object inside the Sherlock class. Whenever the size of John changes, the size of Sherlock changes.
But wait, I thought Isn t the idea of data abstraction being able to change one without changing the others? Isn t it supposed to be really cool to be able to provide different implementations without changing class definitions? Isn t it about time you asked your doctor about [latest pill for old people]?
Form from function: the handle We shall be changed. In a moment, in the twinkling of an eye -- The Trumpet Shall Sound, Messiah
Seriously, you guys Hiding the implementation of the class within another class is one way to preserve the interface and minimize recompilation required for client classes How does all this work?
#ifndef HANDEL_H #define HANDEL_H #include <cstdio> #include <string> using namespace std; #include "singers.h" class recitative3 public:!recitative3( ) }!~recitative3( ) }! string sing( ); private:! singer singer_; }; #include <iostream> #include <cstdlib> using namespace std; #include "singers.h" #include "handel.h" string recitative3::sing( )! return singer_.sing( ); } int main( void )! recitative3 levi;! cout << levi.sing( ) << endl;! return 0; } #endif 28
#ifndef SINGERS_H #define SINGERS_H #include <string> using namespace std; class SingerImpl; class singer public:!singer( );!~singer( );! string sing( ); protected:! SingerImpl* impl; }; #endif #include "singers.h" #include "singerimpls.h" #include <cstdlib> singer::singer( )! impl = new SingerImpl( "And he shall purify the sons of Levi" ); } singer::~singer( )! delete impl; } string singer::sing( )! return impl->sing( ); } 29
#ifndef SINGERIMPLS_H #define SINGERIMPLS_H #include <string> using namespace std; //#define CHANGED class SingerImpl #ifdef CHANGED public:!singerimpl( string _str, int _times=3 );!~SingerImpl( ) }! string sing( ); protected:! string str;! int times; #else public:!singerimpl( string _str );!~SingerImpl( );! string sing( ); protected:! string str; #endif }; #endif #include "singerimpls.h" #include <cstdlib> #ifdef CHANGED SingerImpl::SingerImpl( string _str, int _times ) : times( _times ), str( _str ) } string SingerImpl::sing( )! string result;! for( int i=0; i<times; i++ )!!! result += str;!! result += "\n";!}! return result; } #else SingerImpl::SingerImpl( string _str )!: str( _str ) } string SingerImpl::sing( )! return str; } #endif 30
Ahh, the memories What did we learn? Changes to the size of SingersImpl Had to recompile singersimpl.cpp singers.cpp Did not have to recompile handelsings.cpp even though it used the recitative class, which contained an object of type singer Changes to the implementation only of SingersImpl Have to recompile singersimpl.cpp only
Good vs. Evil Advantages of Handles Clients have to only re-link to object code (recitative class) Hides all implementation from the end user (good for proprietary code) Clients do not waste time performing recompiles Disadvantages No member functions of the class can be inline All interface classes have this extra impl pointer One extra level of indirection Cost of creating/destroying the handle Evil will always triumph because GOOD is DUMB -- Spaceballs
Release-to-release binary compatibility (RRBC) Using old executable files (dll s and exe s) when portions of the system have changed Objective Adding new member functions or data members should not result in a required recompile You must follow guidelines to do this: Existing class hierarchy cannot change New virtual function declarations must come after the old ones All old virtual functions must remain in the same order (and cannot be deleted) Previously existing public and protected functions may not be deleted The total size of a class instance must remain the same
RRBC (2) So, you have to keep everything you had before, but if you add something, the size has to remain the same? // rated_r.cpp #include <iostream> void main( void ) char q[5] = ; q[0] = 87; q[1] = 84; q[2] = 70; q[3] = 63; cout << q << endl; return; }
The Handle See, this is where the handles come in Put all of your changes in with this stuff that people never see Their size will always stay the same Our implementation can change as much as we like We can add member functions to our heart s content.
Object creation control Recall those famous lists from the beginning of the class Lists of pointers Restriction: DO NOT INSERT OBJECTS CREATED ON THE STACK Why was this? Because the objects get cleaned up when they move out of scope, and you re risking bad memory What did we say? It s hard to enforce that What is the truth? It s pretty easy to enforce that
How? Make the destructor private This means that when an object goes out of scope, it cannot be deleted Thus, stack created objects will not compile Heap created objects can be compiled, and should be deleted through some special function
Now, the converse How can you restrict usage of the new operator?
The funny thing... You know what the funny thing is about designing a class hierarchy, replete with class data members, virtual functions, complex attributed return values, and wanting to optimize it given a set of criteria? It s the little differences.
Expensive operations Temporary Objects! Owww! You want to avoid these as much as possible Pass by const reference or by const pointer Do not create any temporary objects that you *may* need until after you ve done your conditional checks Use the copy constructor whenever possible You can always make this faster using copyon-write
Proxies Almost, but not quite, exactly like copy-on-write Allows for the passing along of information from some master object that is big and should not be copied lightly, or should be centralized, due to data synchronicity Specifies what should be done to objects when information is written to the base Copy-on-write Specifies that a new object should always be created Pointers Specify that the existing object should be modified??? Specify that under certain conditions the existing object should be modified, but others it should be copied locally
Making complex from the simple When you are abstracting a system that has easy to understand functionality that builds on itself, take advantage of that Good example: comparative operators