• Advertisement
Sign in to follow this  

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

This topic is 3534 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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]

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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.hpp
class 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.hpp
class 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]

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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!

Share this post


Link to post
Share on other sites
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?


Ignoring that "in your future programs" you won't be messing around with toy examples like Cat ;)

In C++, there are lots of rules of thumb of the form "Use X when you can; use pointers when you have to." With good reason :) You have to do your own memory management, and raw pointers don't "know" anything that could help with this task. For example, if you wanted to write a function that accepted a pointer and deallocated the pointed-at stuff (very bad idea), you'd be stuck, because there is no way to find out whether the pointer points at a single element or at an array allocation, and you *must* use delete[] for the array allocation rather than delete. (Even if the array size is 1, even if it's an array of a primitive type, even if anything-else-you-can-think-of-that-might-make-a-difference.)

So yeah, pay attention to the other replies, and think carefully - sometimes you will need a pointer, and you can't do this stuff on autopilot.

Share this post


Link to post
Share on other sites
Quote:
Original post by rip-off
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.
Ironically, you forgot the single most common scenario: You simply want the object to stick around after the current scope has exited. It doesn't matter how simple the object is, or whether you're using it polymorphically; if you want it to exist 'independently' of the code that creates it, then you need to put it on the heap (or make it a global variable, but there are other issues inherent in doing that). An example would be creating nodes in a linked list structure - they're small objects and not polymorphic, but you need them to exist independently of any AddNode() function.

Using stack objects instead of heap objects is often a sensible practice, but don't go crazy.

Share this post


Link to post
Share on other sites
Quote:
Original post by superpig:
It doesn't matter how simple the object is, or whether you're using it polymorphically; if you want it to exist 'independently' of the code that creates it, then you need to put it on the stack

This is probably a missprint, or perhaps I'm missunderstanding him. If you want your data to exist independant of the call stack, then you need to create it on the heap, not the stack. If its created on the stack then it's local data which will be destroyed upon returning from the current location.

Also, I will be posting a quiz for this week tomorrow. I would do it today, but I'm still catching up on my reading. You will, however, find that I posted a quiz for Week 4.

Share this post


Link to post
Share on other sites
Quote:
Original post by jwalsh
Quote:
Original post by superpig:
It doesn't matter how simple the object is, or whether you're using it polymorphically; if you want it to exist 'independently' of the code that creates it, then you need to put it on the stack

This is probably a missprint, or perhaps I'm missunderstanding him. If you want your data to exist independant of the call stack, then you need to create it on the heap, not the stack. If its created on the stack then it's local data which will be destroyed upon returning from the current location.

Also, I will be posting a quiz for this week tomorrow. I would do it today, but I'm still catching up on my reading. You will, however, find that I posted a quiz for Week 4.


Whoops, yes. Sorry, I'll edit my original post...

Share this post


Link to post
Share on other sites
Greetings All!

It's once again QUIZ TIME!!! That's right, listed below are a set of quiz questions to help you test your knowledge and understanding of the material contained in chapters 6. I will post a follow-up quiz for chapter 11 tomorrow.

In addition to the questions and exercises below, make sure that as you're reading the book you enter the examples into your compiler, build the program, and run the executable. I know this is a time consuming process, but the repeat use of keywords, syntax, and semantics will help ingrain the information into your long-term memory. My advice is to create a simple "driver" project with a function main. As you read, enter the examples into function main, test it, and then erase it for use again in the next example.

PLEASE DO NOT POST THE ANSWERS TO THESE QUESTIONS OR EXERCISES. If you are unable to answer these questions, please ask for assistance, but DO NOT POST THE ANSWERS. Any question which is not marked with [Extra Credit] can be answered by reading your textbook. Questions which are marked [Extra Credit] either have been answered in the thread previously, or can be answered by doing a bit of research.

I will create an answer thread for these questions immediately, so that people will have a chance to get the answers more quickly.

Chapter 6 Quiz

1. What was the ‘C’ capability which allowed you to combine related variables? Did this solve the problem of connecting data with behaviors?
2. How do you make a new type in C++?
3. What is a class?
4. What is another name for variables within a class? What is another name for the member functions?
5. What are the requisite parts of a class declaration? Show an example.
6. What are the two things a class declaration tells your compiler?
7. When discussing naming conventions, what’s the most important point to remember?
8. What is an object?
9. How do you declare an object? Show an example.
10. What operator is used to access members and methods of a class for objects created on the stack.
11. What is the difference between public and private members/methods within a class? Which is the default?
12. In general, should methods or members be private? Why?
13. What is the primary way you initialize the member data of a class?
14. How can you identify a constructor for a class?
15. What is a default constructor? How is it called.
16. What does “declaring a method of a class const” mean? What is the syntax for such a declaration?
17. In the context of classes and objects, what is a client?
18. Where do class declarations go? Where do the member function definitions go?
19. When one class has a member variable which is an object of a different class they are said to form a ___ relationship?
20. What is the only difference between C++ classes and C++ structs?

Chapter 6 Exercises

I will post exercises tomorrow for both chapter 6 and 11.

Cheers and Good luck!

Share this post


Link to post
Share on other sites
It's been a hard week! I'm comfortable with the principles in the chapters but have been very confused by Listings 6.8 and 6.9 which demonstrate the use of Classes within Classes. There are so many methods and data members there and a lot of them have similar names. Does anyone have a more simple example that they could post with, maybe, 2 or 3 methods and 4 or 5 data members?

I realise that this is a core element of C++ and I MUST get my head around it, otherwise I'll stay at the "Hello World!" phase!

Share this post


Link to post
Share on other sites
CondorMan,

I must say I have to agree! That rectangle program is pretty crazy, although I've been kinda hoping Mr. Walsh's workshop will create other, simpler examples for us to work with in the coming weeks. Perhaps someone else could post a simpler program illustrating subclasses for us to examine here? I would find it very helpful as well.

Palejo

Share this post


Link to post
Share on other sites
Well, if you don't ask, we can't guess what you're having problems with. Please, people, if you have a question, if something is unclear, say so. Even if it seems a minor point, ask.

If nobody ever asks questions, the tutors are going to believe that noone is actually participating in the tutorial, and lose interest.

Watch this space for an explanation of nested classes, coming up when I'm done typing it.

Share this post


Link to post
Share on other sites
Please note that it's probably going to take a while to finish typing that explanation. While nested classes are simple, they open the door to some relatively advanced techniques which are omnipresent in the standard C++ library. In fact, template metaprogramming, which is possibly the most advanced C++ programming technique could not exist without nested classes.

And rating++ to CondorMan, for giving me back hope that we're not just wasting our time.

Share this post


Link to post
Share on other sites
You are absolutely not wasting your time, I am sorry I didn't bring up my semi-confusion earlier I just felt once I started actually programming these concepts would hit home for me. But more explanation certainly can't hurt! Thank you again.

Share this post


Link to post
Share on other sites
Anyway, nested types are just what they sound like they are: a type definition within a type definition. If you put a class Key within a class Door, you have just created a class which really is named Door::Key.

class Door
{
public:
class Key {}; // This is really Door::Key
};




This is most obvious when you separate declaration and definition:

class Door
{
public:
class Key; // Declaration for the nested class;
};

class Door::Key
{
};



When you use nested types, it makes it obvious that the two types are related somehow. Using the above example, you could have several types of door, each with its own type of key. (Yes, I know it really should be a Lock and a Key.)

class Door
{
public:
class Key {};
};

class MagicDoor
{
public:
class Key {};
};

class ElectronicDoor
{
public:
class Key {};
};

The two types are coupled more tightly than if you had just relied on a naming convention like <tt>Key</tt>, <tt>MagicKey</tt> and <tt>ElectronicKey</tt>.

Note, incidentally, that you can also use a <tt>typedef</tt>

class Matrix
{
public:
typedef float scalar; // The type of values used in the matrix
};



Users of your matrix class (which could be you or somebody else, think professionally) can use Matrix::scalar instead of explicitely float, thus making sure they always have the right type. If you change your scalar type, their code will pick it up without requiring any change (as with other uses of typedef), but since the typedef is confined to your Matrix class, it won't conflict with other names that might be defined in the program.


Now, one of the immediate benefits of nested types is that the nested type is subject to any access restrictions that the enclosing type might impose. If the inner type is private, it can only be used from within the outer class.

class Outer
{
private:
class PrivateInner {};
public:
class PublicInner {};
};

Outer::PrivateInner a; // Error, it's a private member.
Outer::PublicInner b; // OK. it's a public member.




Nested types thus allow you to declare whole classes as being "implementation details" of some other class, simply by making it a private nested type.

The most familiar example of this would be the traditional linked-list class, where the list is composed of a series of nodes linking to each other (the details can rightfully be considered an advanced topic at this point, so I will not elaborate). Except in the most naive implementations, such as those written by beginners, the nodes themselves aren't directly accessible. They are implementation details users of the list need not be aware of. Nor should they be granted indiscriminate access, since careless manipulation could easily break the list. Anyone who has implemented such a data structure, or a more complex one, knows that you don't want people messing with your pointers.
So, you make those nodes private within the list class, and create a second nested type with a more restricted interface, which you make public:

class LinkedList
{
private:
class Node
{
/* This is where the magic happens. */
};
public:
class Iterator
{
/* Provides a friendly interface to the list's elements */
};
};



More to come, ask questions if I am being unclear.

Share this post


Link to post
Share on other sites
Unsurprisingly, this is the approach taken by the Standard C++ Library container classes:
#include <list>

int main()
{
std::list<int> mylist; // a list of ints.
std::list<int>::iterator itor; // an iterator over a list of ints.
}



The <int> indicates that we are dealing with a list of int. std::list is a class template. It is a model from which the compiler can generate multiple list classes, depending on the type of the element it is intended to hold. Writing your own templates is very much an advanced topic, but using the ones that are found in the standard library is very simple. In fact, you can't pretend to know C++ if you don't know how to use the standard library properly. Unfortunately, few beginner C++ books cover it properly. Keep that in mind as we go on with this workshop.

[advanced]

Long story short, one of the most powerful techniques involving nested types is that you can use them in generic code:

#include <list>
#include <string>

template<typename Container> // Container is a placeholder for the type
// that will actually be used in the function.
void print(const Container& c) // print is thus a function template.
{
typename Container::iterator itor; // We don't know what the Container is, but
// we want its nested 'iterator' type!

for(itor=c.begin() ;itor != c.end(); ++itor) // use the 'friendly' interface
{
std::cout << *itor << std::endl; // note that we don't even know
// the type of the data we are
// printing!
}
}

int main()
{
std::list<int> list_of_ints;
std::list<std::string> list_of_strings;
std::deque<char> deque_of_chars;

// Assume we have code to put data in those containers

print(list_of_ints); // calls void print<std::list<int> >(const std::list<int>& c);
print(list_of_strings); // calls void print<std::list<std::string> >(const std::list<std::string>& c);
print(deque_of_chars); // calls void print<std::deque<char> >(const std::deque<char>& c);
}



In generic code, nested types allow you to refer to related types, such as a container's iterator type even though you did not specify exactly what type of container you were using.


Though rather advanced for now, this is one of the fundamental concepts the C++ standard library is built on: each container "knows" what its iterator type is.

[/advanced]

Please remember to ask questions.

Share this post


Link to post
Share on other sites
[ADVANCED]
Note that you don't always have to nest classes to make that kind of "association". The real advantage of nested classes comes from the *type declaration*, especially in template metaprogramming. However, if templates *are* involved, you will probably find it difficult to make things work in "the other way", which is:

- Inside the class, typedef the associated type.
- If the associated type should be "hidden", *don't mention it in the header file*.

Also note that while the class declarations are nested, in C++ there is no implication of objects being nested: Door objects do not include a Door::Key object as a data member, base class or anything else, and neither are *specific* Door::Keys "associated with" any *specific* Door object, simply because of the declaration of the nested Door::Key type. Some languages do provide that sort of "binding" for you. However, this does cost in terms of memory usage, "provides something" that you didn't "ask for", and is sometimes not what you want; all of this goes against the C++ design philosophy, so it doesn't happen in C++.
[/ADVANCED]

Share this post


Link to post
Share on other sites
Thank you Fruny - it's starting to make sense now that you've given some logical examples. The examples in the book went way over my head because there were so many similar terms in the classes.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement