Make your Data Type more Abstract with Opaque Types in C

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
today we're talking about abstraction abstract data types and opaque types in c hey everybody welcome back so i was recently working on a video about testing unit testing specifically and this isn't it but it's coming don't worry it's coming in about a week but testing got me thinking about abstraction and some things that new programmers and students of computer science don't often think about or maybe it's something that you see and you're a little confused about what's going on now this video will have code in it and of course as always that code is available through patreon but first let's talk a little bit about abstraction so abstraction is a really simple idea it's basically the idea that we want to hide or abstract away inner implementation details in order to make cleaner easier to manage interfaces between program components so here's an example say i'm writing an audio processing program and i create a module that reads in audio files in a certain format let's say wav files and provides the waveform data from the file to the rest of the program okay this module is doing a lot of file operations and format parsing and if i if it were something other than wave format it might be handling compression i mean decompression you get what i'm saying but the point is is that the rest of my program doesn't really care about the details they don't really care about what happens inside this module the rest of my program just doesn't care about the details it just cares that it can put in a file name file name to a wav file and it's going to get back the audio data if that file exists and if i do a good job of keeping it self-contained and hiding the inner details and only allowing access to its functionality through a select group of functions then this allows me to do two important things one i can change those inner workings at any time and the rest of my program doesn't have to change it doesn't even have to know that anything changed and as your projects get bigger and more complex this becomes a huge advantage and two it makes it much easier for me to take this module that i've created and copy it over to another project and reuse that code either in the form of a library or by just copying the source code over and this is such a common thing in programming and computer science that a lot of languages especially your object-oriented languages actually build mechanisms right into the language for hiding information for example if you use java or c plus your classes can have private data members the whole point of this is to hide the inner workings the details to restrict outside components and only allow them to access the public members but students sometimes wonder if you can do the same thing in c which is not an object-oriented language and doesn't have a private keyword so today i want to talk about one way that you can do this using opaque data types now you have probably all used opaque data types before when you open and close files you just might not have realized it when you call the f open function you give it a file name and a mode read write or append and it returns a file pointer and then you use that file pointer when you call f right and f read and f print f and finally when you call f close to close the file but what is this file pointer it's a pointer to something clearly probably a struct but what are its members what is going on inside we don't know the type is opaque yes you could dig into the libsy source code and you could try to figure it out and that's not a bad educational exercise but this set of functions is designed to hide the details of how files are handled we don't need to know what's going inside we just get a pointer to our open file and we use it whenever we need and that's great because it allows the underlying system to implement files in different ways i mean maybe the most efficient way to implement open depends on your operating system maybe it depends on your processor and this nice abstraction allows me to implement this struct and these functions in any way that i like internally and i don't have to care as long as i get the data that i want from my files so let's take a look at how you could do this in your own code so let's start out with a slightly modified version of the q example that we worked on in a recent video i'll link down below in the description if you missed it and you want to check it out so the main changes that i've made since that video is i changed the names a bit to make them a little more consistent and a little shorter i also made this creation function so that it returns a pointer to a cue rather than taking it in as an argument because that's going to make our example today a little more straightforward and i modified this q destroy function down here so that it not only frees the arguments but frees the queue itself because basically i am mallocking space for this cue when you call cue create so this is just making sure we don't end up with memory leaks and we're cleaning up the stuff that we're allocating but otherwise this code is basically the same as you saw before in that previous video okay now in this code and in that video i just put everything in the same source file you can see that all of my q functions are up here and i have my main function that actually uses the queue down here because in that video my goal was to as simply as possible show you how the queue implementation works but if we're talking about abstracting away data types i didn't do a very good job of doing that because i just really didn't abstract the details away from this user if i'm the user of this queue all of the details are right up here they're all right in front of me and while i did only call the prescribed functions down below there's nothing but my common sense that prevents my main function from directly accessing the elements inside my queue like my head my tail my numb entries or size and that's the sort of thing that a good abstraction or abstract data type would like to prevent so let's see what we can do with it i'm going to leave my original version here in this ridge directory that's fine i've made this abstracted directory over here let's just copy the code over but i'm going to break it up into a few different files okay so the first thing i'm going to make is i'm going to make a header file we'll call this myq dot h let's also create a dot c file same name and let's make a q test dot c that's going to actually test out my queue and see if it actually works okay so if we're coming back to my original example i'm going to take my main function here and we're going to copy that and we're going to put that in q test and we're probably going to need some header files up here let's see what we had over here just copy these over for now i may not need all of them probably don't need limits that's cool and let's also include our myq.h file because that's going to have the information about our queue then we're going to need that in this in this function now the bulk of my q functions down here these guys i'm going to put in my dot c file let's just copy this all i'm going to copy them all over into my q dot c okay we're also going to need to include my q.h that's going to tell them where it is now you notice i've got a bunch of squiggly lines here because it's saying i don't know what q is i don't know what q is and so let's jump back down here and we will actually define what our q is along with this little pound to find up here in my queue dot h i'm also going to add some if def cards my cue and if you've never seen if deaf guards before i do have a video on that i will link down in the description it's just basically to prevent any multiple includes if there are multiple different parts of my program that want to actually utilize this queue and then the only other thing i need to do here is i need to go through and actually create prototypes for my functions in the header file because the header file is going to be the interface that i'm going to use to communicate to the outside world what they can and cannot process so i'm just going to take all of these functions and copy them over into here but i just want prototypes so what i'm gonna do is just cut the function bodies out of them and do this quickly here okay now these are my public functions now when i say public function what i mean is any function that i want to allow users of this module users of this data structure to access outside of this translation unit and right now all of my functions are listed as public let's say that there are some that i didn't want like maybe i don't want my users to be able to test whether my queue is empty or full i could move these into the dot c file and declare them as static that's going to limit their scope to the current translation unit and it's going to make it so that outside users can't call those functions so i could do that but in this case it doesn't make much sense because i really do want my users to be able to test for fullness and emptiness so we're going to leave them in here and since all of these functions use this queue type then of course we have the cue struct up here that allows people to create cues and that seems like a natural place to put it now at this point let's pause for a second because things are feeling a bit more abstract i've separated these components into different files and the header file now typically represents my interface that's the thing that tells you what you can access and what you can't which is basically the abstraction that i want to provide the user and let's just make sure that it actually works so i have a make file that i created for this there's really nothing fancy here it just compiles these and then links them all together in a final binary it would be helpful if i went down and went into my abstracted folder and tried to run it oh my makefile is looking for q test here we'll just rename this to q test and then let's see okay what did i forget clearly we have a few issues we'll get those sorted out really quick oh yes we've got to include standard bool up here because i'm using bools okay one thing about when you're debugging you always want to look at the first error that's showing up that's really important okay probably another header file here yes here let's go back down just grab these guys for time's sake and i'm probably also going to need to move this into my header file because notice i use int min right here the whole purpose of having this limits.h is because that's where inman is defined so now we compile we're just fine and we can run our q test function our program and you can see that our q is working just fine we've just separated things out of it now this works and it's definitely a step in the right direction because now if you want to see the inner workings you have to actually like look into the program but if we look back in the header file this q type right here is still kind of exposing its inner workings it's really not opaque you can tell exactly everything that's in there and our main function over here in q test could actually it's not currently but i could just say something like q1 head equals seven i mean it would be a really bad idea to do it but i could there's nothing that prevents that i could totally do this because the inner working the inner elements of this struct are totally exposed and so what do we do in this case we need to define our struct so these function definitions will compile so they need to know that a queue is a thing but we don't want to give away the farm by telling the user what's inside the struct and that's where opaque data types come in what i'm going to do is i'm going to take my main struct definition here and move it back into my q dot c okay so so now i'm moving that inside here and actually while i'm at it we're just gonna put it back over here so so i have a version in both but what i'm gonna do in the header file is remove the body of the struct and i'm gonna have to actually give the struct a name to make my syntax still work so i'm going to say there's some some struct called myq let's give it a name my queue over here this is internal no one's actually ever going to use my cue but i'm just i'm just using it so that i can declare this type the way i want to but so we're basically going to have this my cue struct which we're type defining to be a queue so basically back here in the header file i'm going to type def struct my queue to be q okay now this tells the compiler that q is really just some sort of struct called myq and to anyone who's using this queue the compiler will treat q as an incomplete type meaning that we know it's a type we just don't know the details except that it's a struct and now at this point q is what i would call an opaque data type it's defined incompletely and so users can't just access what's inside the struct i mean they could there are some sneaky ways with casting and point arithmetic they could still access the memory the elements inside the struct but we shouldn't and we won't and that sort of thing could be done with private class elements in c plus as well so i guess the point of what we're doing is this it becomes a deterrent it becomes more complicated to do the wrong thing so really by doing this we're saying hey these are not the elements you're looking for you don't get to play with what's inside this struct you don't need to look behind the curtain just use the cue okay and this will work even though it may seem a little bit strange now some of you may be wondering why the compiler lets us do this and we haven't actually specified the type and it's a good question there's not a lot you can do with an incomplete type you can't access its elements you don't even know how big it is but you'll notice that these functions in our header file don't ask for a queue they ask for a cue pointer a pointer to a queue and a pointer is just an address so the compiler knows what to do with an address it knows what to do with a pointer just as long as you don't try to dereference that pointer so this allows our users to call our functions and store pointers to cues but doesn't allow them to come down in main and do stuff like q1 head equals 45. if i try to do that you notice it's going to give me a problem it's going to say a pointer to an incomplete class type is not allowed but just so you can see that this actually does work i can still compile it everything is fine so long as i don't try to access those inner members and my q test program still works just as it did before so that's one more way that you can make your code more abstract you can make your data types more abstract without switching over to something like c plus plus or java basically without having to switch languages because sometimes you don't want to switch languages but you still want to be able to limit the scope of your data types in order to maintain better abstractions and keep things more modular and easier to maintain in the future i hope this is useful that testing video is coming up hopefully next week subscribe if you don't want to miss that or the video that comes after that and until next week i'll see you later
Info
Channel: Jacob Sorber
Views: 13,172
Rating: 4.9687095 out of 5
Keywords: abstract data type in c, abstract data type in c language, abstract data type, adt in c, c adt, c abstract type, c abstract data type, data structure, opaque types, opaque types in c, opaque type in c, opaque pointers, queue, queue data type, queue data structure, queue in c, c language, c programming tutorial, c types, incomplete types, c opaque type, abstraction, hiding details in c, Header files, queue data structure in c, adt in c language, queue in c using array
Id: TsUOhPsZk6k
Channel Id: undefined
Length: 13min 40sec (820 seconds)
Published: Tue Apr 06 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.