C++ Workshop - OO Analysis, Design, & Porgramming (Ch. 6 & Ch. 11)

Started by
75 comments, last by Dbproguy 15 years, 11 months ago

Welcome to the GDNet C++ Workshop – Ch. 6 & Ch. 11

For a complete introduction to this workshop, please look here. Workshop Overview This workshop is designed to aid people in their journey to learn beginning C++. This workshop is targeted at highly motivated individuals who are interested in learning C++ or who have attempted to learn C++ in the past, but found that without sufficient support and mentoring they were unable to connect all the pieces of this highly complex but powerful programming language. This is a 'guided' self-teaching C++ workshop. Each student is responsible for taking the time to read the material and learn the information. The community and tutors that arise out of this workshop are here for making the learning process run more smoothly, but are not obligated to baby-sit a person's progress. Because everyone will be working from the same textbook (Teach Yourself C++ in 21 days 5th Ed.), students may find it easier to get answers to the specific questions they might have. There is no minimum age requirement, and there is no previous programming experience required. Additionally, this workshop does not attempt to defend C++ as a language, nor does it attempt to demonstrate that C++ is either more or less useful then other programming languages for any particular purpose. People who intend to start a discussion about the differences between C++ and ANY other languages (except as are relevant to a particular discussion), are encouraged to do so elsewhere. This workshop is for educational, not philosophical discussions. Quizzes & Exercises Each week will have quizzes and exercises posted in the weekly threads. Please try and answer them by yourself. As well, please DO NOT post the answers to Quizzes and Exercises within this thread. Once it becomes acceptable to post the answers to quizzes and exercises, an additional thread will be created each week specifically for the purpose of posting quiz answers. If you try with reasonable effort but are unable to answer the questions or complete the exercises, feel free to post a clarification question here on the thread. Tutors, myself, or others will do the best we can to point you in the right direction for finding the answer.

Chapter 6 – Understanding Object Oriented Programming & Chapter 11 - Object Oriented Analysis & Design

Introduction At long last we have arrived at the meat of C++. There will be much after this chapter, such as chapter 7 (More on Program Flow) which can still be inherited from C, but the majority of chapters after this point will either be wholly C++ in semantics, or will at least provide an alternate implementation for use specifically with C++. Based on the feedback provided in our forums I've decided that during the remaining weeks of the course there will be 2 or 3 more times when it is appropriate to combine two chapters into a single week. This is such a week. Although Chapter 6 is perhaps the most important chapter in the book, chapter 11 is so closely related that it can be viewed as a continuation of the subject. Where chapter 6 introduces the reader to Classes and the syntax to use them, chapter 11 goes beyond that into thinking in terms of objects, and shows the reader how to design their applications with objects in mind, rather than procedures. Because we will be doing two chapters this week I expect there will be many more questions. Please feel free to post them here, as it's very important that you grasp the concepts. Without the tutors and peers here to help I'd be concerned about trying to do these two chapters at once, but with our help, they should both go relatively smoothly. Please remember to use OPINION and WARNING tags whenever applicable. As well, feel free to post your own insights, and review questions or exercises beginning Wednesday or Thursday. Outline of the Reading - Chapter 6
  1. Is C++ Object Oriented?
  2. Creating New Types
  3. Introducing Classes and Members
  4. Accessing Class Members
  5. Private vs. Public Access
  6. Implementing Class Methods
  7. Adding Constructors & Destructors
  8. Including const Member Functions
  9. Interface vs. Implementation
  10. Where to Put Class Declarations and Method Definitions
  11. Inline Implementations
  12. Classes with Other Classes as Member Data
  13. Exploring Structures
Outline of the Reading - Chapter 11
  1. Building Models
  2. Software Design: The Modeling Language
  3. Software Design: The Process
  4. Step 1: The Conceptualization Phase: Starting with The Vision
  5. Step 2: The Analysis Phase: Gather Requirements
  6. Step 3: The Design Phase
  7. Step 4-6: Implementation, Testing, Rollout?
  8. Iterations

Good Luck!

[Edited by - jwalsh on May 30, 2007 12:09:18 PM]
Jeromy Walsh
Sr. Tools & Engine Programmer | Software Engineer
Microsoft Windows Phone Team
Chronicles of Elyria (An In-development MMORPG)
GameDevelopedia.com - Blog & Tutorials
GDNet Mentoring: XNA Workshop | C# Workshop | C++ Workshop
"The question is not how far, the question is do you possess the constitution, the depth of faith, to go as far as is needed?" - Il Duche, Boondock Saints
Advertisement
Hello everyone! First grats to Mr. Walsh on his child, I hope he is well. Got through chapter 6 this morning feeling good, but I have a quick question regarding .hpp files.

The book states on pp. 162-163 that we should include class declarations in .hpp files, and use #include in our .cpp files to insert those declarations as necessary. This makes sense to me.

What confuses me, on the other hand, is the definition of class methods. The book on these pages seems to imply that you'd want to include your class method definitions in your actual .cpp files. Why not simply include these in your .hpp file as well, with your class declaration? By not doing this, don't you have to always 're-define' your class method definitions whenever you #include your .hpp file in a new pgm, thus creating redundant code?

I have been reading through Chapter 6 and something really confused me. The way to instantiate a Class was coded as:

Cat Frisky(5);


where I would expect something like:

Cat Frisky = new Cat(5);


Maybe I am confused because of my Java background, but I am sure that I have seen it done in C++ this way too. Is this last example also possible? If so, what is the difference with the first?
Quote:Original post by RinusMaximus
I have been reading through Chapter 6 and something really confused me. The way to instantiate a Class was coded as:

Cat Frisky(5);


where I would expect something like:

Cat Frisky = new Cat(5);


Maybe I am confused because of my Java background, but I am sure that I have seen it done in C++ this way too. Is this last example also possible? If so, what is the difference with the first?

Note that the second should be Cat Frisky = Cat(5);. new allocates on the heap and returns a pointer (would be Cat *Frisky = new Cat(5);).

That said, both are possible. [Opinion] The latter may be recommended because it avoids ambiguities later on (read below) [/Opinion].

[Advanced]
- In certain cases, the first form will be interpretted by the compiler as the prototype to a function. Example:
int i();
i = 5; // Compiler error: function as left operand
Read more here

- One may think that Cat Frisky = Cat(5); involves a useless extra copy, but any decent compiler will optimize it out.
[/Advanced]
Quote:Original post by Palejo
What confuses me, on the other hand, is the definition of class methods. The book on these pages seems to imply that you'd want to include your class method definitions in your actual .cpp files. Why not simply include these in your .hpp file as well, with your class declaration? By not doing this, don't you have to always 're-define' your class method definitions whenever you #include your .hpp file in a new pgm, thus creating redundant code?


Imagine you are working on a large project composed of 146 .cpp files, 97 of which #include "foo.hpp". When you compile your project, it means that the code for you member functions gets compiled 97 times. One each for each cpp file that includes the header. On the other hand, if the member function definitions are in their own cpp file, they only get compiled once.

Now imagine you make a change to one of those member functions. For example, you change a + to a - in some expression. Since the headers are literally included into the cpp files by the preprocessor, it means that all 97 files have now changed and need to be recompiled! If the member function had been in foo.cpp, only that source file would have needed to be recompiled, after which the whole project would only have to be relinked.

By placing functions definitions into a cpp file, you insulate the rest of your project from changes to the function implementations. Furthermore, foo.cpp could #include whatever other header files it needs to compile without any of the 97 files that include foo.hpp inheriting that dependency -- thus further minimizing the chance that they would need to be rebuilt.

Furthermore, foo.cpp could contain a host of non-member functions used to facilitate the implementation of the class members without having to expose any of them to the rest of the project.

[advanced]
Some techniques such as the Bridge (or pImpl) design pattern go even further in that direction.

Of course, there are advantages to having member functions inlined in your header file, and some constructs like templates actually require it, but that discussion is probably best left for later.

Finally, solving the often-encountered problem of circular dependencies (class A needs class B, class B needs class A) generally requires separating the member function definitions from the class definition, since they require the full definition of the other class (because they actually manipulate it) while the class definition can be made to only require a declaration (so long as it doesn't need to know the size of the other class).

Note: A type that has been declared but not defined yes is known as an incomplete type. They can only be used in limited fashion.

foo.hppclass Bar;  // Warn the compiler that yes, there exist a class called Bar.class Foo{   Bar* barptr;           // The size of a pointer does not depend on what it points to.public:   Bar do_stuff(Bar& b);  // Function prototypes work fine with incomplete types.};bar.hppclass Foo;class Bar{   Foo* fooptr;public:   Foo do_stuff(Foo& f);};foo.cpp#include "foo.hpp"#include "bar.hpp"  // Writing a function that actually create or manipulate an                    // instance of a type requires a full definition at that point.   Bar Foo::do_stuff(Bar& b){   // code goes here}bar.cpp#include "bar.hpp"#include "foo.hpp"   Foo Bar::do_stuff(Foo& f){   // code goes here}

[/advanced]
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Quote:Original post by RinusMaximus
I have been reading through Chapter 6 and something really confused me. The way to instantiate a Class was coded as:

Cat Frisky(5);


Local and static variables are treated in C++ in a similar way as primitive types like int or float are in Java, as opposed to reference types like Integer or Float.

Unlike Java, user-defined C++ classes can be created that way, and either passed to functions by value (which creates a local copy within the function) or by reference (which doesn't).

void pass_by_value(Cat c);
void pass_by_reference(Cat& c);

Quote:Maybe I am confused because of my Java background, but I am sure that I have seen it done in C++ this way too. Is this last example also possible? If so, what is the difference with the first?


Cat* Frisky = new Cat(5);

Would be somewhat closer to what you are familiar with in Java, aside from the fact that C++ doesn't perform garbage collection and thus anything that is allocated with new must eventually be destroyed with delete.

When passing variables to a function, passing a pointer is also the closest way to mimic Java's behaviour. That should give you a hint about how Java works internally. [smile]

void pass_a_pointer(Cat* c);



I don't think I've been overly clear with my explanation, so if you don't understand something, feel free to ask for further clarification.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Yeeks - that's a surprise!

I made a start on reading chapter 6 over the weekend as there was a lull in contributions relating to chapter 5. It is certainly more challenging than the preceeding chapters and then I saw that this week covers chapters 6 and 11. I'll have to get my learning head on!

I like a challenge and will certainly rise to it.
Thanks for the explanation jflanglois and Fruny, I see what the difference is. And now that you tell me that the new allocates memory on the heap and returns me a pointer it makes sense to me as well. This is actually really close to what Java does.

Is it save for me to assume that the first form
Cat Frisky(5);
is shown because we haven't discussed pointers yet and that
Cat* Frisky = new Cat(5);
will be the way I will use it in my future programs?
Quote:Original post by RinusMaximus
Thanks for the explanation jflanglois and Fruny, I see what the difference is. And now that you tell me that the new allocates memory on the heap and returns me a pointer it makes sense to me as well. This is actually really close to what Java does.

Is it save for me to assume that the first form
Cat Frisky(5);
is shown because we haven't discussed pointers yet and that
Cat* Frisky = new Cat(5);
will be the way I will use it in my future programs?


In general, allocating objects with pointers is only used if you want polymorphic behaviour (which can also be achieved using "references") or if an object is large so placing it on the stack may not be a good idea. It is preferable to use stack objects as much as possible if you can, as they are automatically cleaned up at the end of their scope.

Things allocated with new must be manually deleted ( easy to forget ). There is added confusion in some cases as to who is to free allocated memory or when to delete( a pointer needs to be shared among many different users, if one deletes to early all the others will have a bad pointer, if none delete it there will be a memory leak ).

In practise though, use of specialised objects to help deal with pointers such as the standard library's std::auto_ptr and the boost library's boost::shared_ptr allow you to automate cleanup of objects allocated with new( and reference counting with shared_ptr, very similar in use to java objects ), providing a happy medium between ease of use and functionality.
Quote:Original post by RinusMaximus
Thanks for the explanation jflanglois and Fruny, I see what the difference is. And now that you tell me that the new allocates memory on the heap and returns me a pointer it makes sense to me as well. This is actually really close to what Java does.

Is it save for me to assume that the first form
Cat Frisky(5);
is shown because we haven't discussed pointers yet and that
Cat* Frisky = new Cat(5);
will be the way I will use it in my future programs?


Just wanted to take a second and follow up on that question with a more in-depth explanation of address space.

When your program is executed the operating system sections off 3 different pieces of memory for use by your program. These 3, in no particular order are "Program memory", "Heap memory", and "Stack memory".

Program memory is just what it sounds like. Its memory allocated for the storage and retrieval of the actual instructions of your application. If your application is too large to fit into resident memory, the operating system pages it out to disk and re-loads it as necessary. This is of little concern to you, as the process of paging, etc...is transparent to the developer and only relevant when trying to write spatially optimized code.

Heap memory is where dynamic objects are stored. Dynamic objects can be actual "class objects" or it can even be standard data types which have been allocated dynamically. We'll address dynamic memory more in the chapter on pointers but for now its just important to note that dynamic memory gives access to the actual address of your data in memory, and can be accessed from any scope within your application. That is, a dynamic variable allocated at some level 'N' down in the call-stack can be returned back to the top without fear of invalidating your data. And as previously mentioned, dynamic memory must be manually released in C++ by using the 'delete' keyword.

Stack memory, however is where most 'temporary' variables are stored which are needed only for the current layer of the call stack. By default, all basic data types are created "on the stack." Whenever your scope changes, such as by entering a function or {} scope, a new layer is pushed onto the memory stack. All variables being worked with within that scope exist only on that layer. So if you were to then change your scope by returning from your function, all data at the previous location on the stack is whipped when that layer is popped off the stack. This is why its a bad idea to return the address or a reference to a variable which was created within a function call. If it were created "on the stack" then the address is invalid upon returning from the function. It is important to note, however, that all data created on the stack is cleaned up automatically, without the need to call 'delete' as soon as the current layer of the stack is popped off.

So to answer your question, there are reasons for using both 'local/stack' allocation as well as 'dynamic/heap' allocation. It all depends on how long you're going to need the object, and how expensive it is to create instances of the object. The longer you're going to need it, and the more expensive it is to create, the more likely you are to use dynamic allocation. The less time you're likely to need it (only within the current scope) and the more trivial it is to allocate, the more likely you are to use local instantiation.

We are using local instances at this point because you have not yet learned dynamic allocation, however even upon learning dynamic allocation you will continue to create objects on the stack if you monitor your needs carefully.

Cheers!
Jeromy Walsh
Sr. Tools & Engine Programmer | Software Engineer
Microsoft Windows Phone Team
Chronicles of Elyria (An In-development MMORPG)
GameDevelopedia.com - Blog & Tutorials
GDNet Mentoring: XNA Workshop | C# Workshop | C++ Workshop
"The question is not how far, the question is do you possess the constitution, the depth of faith, to go as far as is needed?" - Il Duche, Boondock Saints

This topic is closed to new replies.

Advertisement