C++ Tutorials

STL

Supreme [H]ardness
Joined
Oct 20, 2001
Messages
4,313
Let's have a thread filled with handy-dandy C++ tutorials, since several people seem to be interested in learning modern, Standard C++.

Integer Data Types
C++ has lots of integer data types, and it can get pretty confusing.

char - This type (I pronounce it "care" as in character; infidels say "char" as in charred, or "car" as in Porsche) is guaranteed to be at least 8 bits long, but may be more. On most systems, it really is 8 bits. char alone among the integral data types may be signed or unsigned, depending on the implementation. You must explicitly request "signed char" or "unsigned char" if you want to be sure. In C++, character constants like 'q' have type char, but char is not special - it's just a narrow integer type.

In C++, a char is a byte, just not necessarily an octet. sizeof reports sizes in bytes, so sizeof(char) is always and forever 1.

short - Guaranteed to be at least 16 bits. On most systems it really is 16 bits. short, like all other integral data types except for char, is by default signed.

int - Guaranteed to be at least 16 bits, and no shorter than short. On most systems is really is 32 bits (true on basically every x86, x86-64, and IA-64 compiler today).

long - Guaranteed to be at least 32 bits, and no shorter than int. long is generally 32 bits on 32-bit systems and 64-bits on 64-bit systems (on x86-64 and IA-64, this is true).

long long - Not a part of the 2003 C++ Standard, but I use it anyways (it will be a part of the next C++ Standard, and is a part of the 1999 C Standard). Guaranteed to be at least 64 bits. Currently, this holds on most systems.

size_t - This is a typedef from <cstddef> which is guaranteed to be the type that sizeof returns. It is a type suitable for holding the size of an array, and is always unsigned.

Now, isn't that nice and confusing? Boost provides <boost/cstddef.hpp>, which has helpful typedefs from the 1999 C Standard that aren't yet in some C++ compilers. boost::uint8_t is guaranteed to be an unsigned 8 bit type (if the compiler provides one), and similarly for boost::uint64_t, etc.

I have found that in writing portable code, having guaranteed fixed-width datatypes is very important, so that the code will have the same meaning whether compiled on 32-bit or 64-bit systems. Therefore, I typedef the boost/cstddef.hpp types to my own, shorter names:

uc_t, us_t, ul_t, ull_t - unsigned 8, 16, 32, 64 bit types
sc_t, ss_t, sl_t, sll_t - signed 8, 16, 32, 64 bit types

My code isn't 64-bit clean yet, but it's getting there.

I think I'll follow up with a Standard Template Library tutorial later.
 
While I applaud your efforts, I am a realist.

People will use this thread as much as the noobies use the HEY NOOB SCUM READ THIS FIRST thread, stickied at the top.

While I don't know why, people would rather create an entirely new topic than read one that already exists.
 
I am looking fwd to the STL tutorial. I need to pick up a book on the STL and learn it better, perhaps this summer...
 
Originally posted by Pianomahnn
While I applaud your efforts, I am a realist.

People will use this thread as much as the noobies use the HEY NOOB SCUM READ THIS FIRST thread, stickied at the top.

While I don't know why, people would rather create an entirely new topic than read one that already exists.

Eh. If it's interesting, people will read it. And not just the noobs. My "Tell us why your Linux distro is the best" thread in Operating Systems is the 5th most read thread in "Other [H]ard Stuffs" (not counting the rules), and it started off much like STL's effort here.
 
The Standard Template Library

The STL is the most significant part of the C++ Standard Library. Roughly speaking, it consists of four parts:

* Containers

* Iterators

* Algorithms

* Strings

To put it as clearly as possible, the Standard Template Library is the triumph of 1990s programming, and any C++ programmer who does not use the Standard Template Library in his work, or have a damn good reason for not doing so, is incompetent. It's as simple as that.

What makes the STL so cool? It's a powerful library for building code. Basically all programming involves several very commonly used data structures and algorithms - sorting, searching, etc. The STL captures most common data structures and algorithms, and provides a clear style for you to extend it with STL-like data structures and algorithms of your own. (Significant omissions from the STL, such as hashed containers, will be included in the next C++ Standard. There just wasn't enough time to get the hashed containers standardized. Other languages, however, are even further behind - Java and C# are still promising generic support period, and when they do get it, it won't be nearly as cool as the STL.)

Now, a fundamental thing about the STL is that inheritance isn't used. Period. None of the containers inherit from anything or each other, and iterators don't either. Everything you know about inheritance is irrelevant when it comes to the STL itself. Of course, the STL can be used with classes that inherit from base classes, and that's a very powerful technique of programming. What the STL does use is template magic, and lots of it.

The STL containers support the Resource Acquisition Is Initialization principle - they manage their own resources, so you don't have to. The STL containers are:

Sequence Containers:
std::vector, std::deque ("deck"), std::list

Associative Containers:
std::set, std::multiset, std::map, std::multimap

Container Adaptors:
std::stack, std::queue, std::priority_queue

These live in the headers:
<vector>
<deque>
<list>
<set> - both set and multiset
<map> - both map and multimap
<stack>
<queue> - both queue and priority_queue

Now, there's also std::string, and I listed it separately above. std::string is weird. That's about the best way you can describe it. It offers a lot of useful functionality, but it doesn't scream elegance like std::vector does, and there are some annoying things about it. This is because std::string was created by committee, whereas Alexander Stepanov was responsible for the creation of the rest of the STL.

Now, the true STL containers - sequence and associative, but not the adaptors - are useful by themselves, but how do they interact with the STL algorithms?

The Key Insight goes like this - STL containers and STL algorithms work so well together because they know nothing about each other. Period. That sounds weird, but when you learn more of the STL philosophy, it makes a lot of sense.

What STL containers do is provide iterators. Iterators are an /abstraction/ of pointers - they take the "pointer" concept and generalize it to the next level. Iterators are a way to step through a sequence or associative container. These iterators are then sent to STL algorithms. Thus, a single STL algorithm can operate on a vector, a deque, or a list without caring what it's actually operating on. This is because the STL algorithms are templated on what iterators you give them.

Most STL algorithms live in <algorithm>. Four useful ones live in <numeric>.

The hardest thing about learning the STL to start off with (and I know, because I only started learning it less than two years ago) is that there are SO many algorithms, and a lot of new concepts flying around. I find that either _The C++ Programming Language, Special Edition_ by Bjarne Stroustrup (or Third Edition, which is the same text, but paperback), or the 2003 C++ Standard, serve as useful references to all of the containers and algorithms. Unfortunately, _C++ Primer Plus Fourth Edition_ by Stephen Prata ends right as it gets in depth with the STL, though it does contain convenient descriptions of some STL components.

_Effective STL_ by Scott Meyers is the best STL help guide I have on my bookshelf.

Now, how do we start off using the STL?

The first Key Insight: Arrays suck. This is modern C++ we're using here - it's not the 1980s anymore. Good programmers don't use arrays and don't new up (or god forbid malloc()) up arrays of objects unless they have a DAMN good reason to (and I know of only a few). When you want to use an array, instead use std::vector.

std::vector is the favorite STL container because it abstracts the concept of the array, is blindingly fast, and is useful in most situations. When in doubt, use std::vector. Only turn to std::deque or std::list if you have specific sequence container requirements.

Vectors can be resized with a simple call - none of that realloc()-and-check-to-see-if-it-worked bullshit. They know their own size so you don't have to remember it. Furthermore, they automatically resize themselves as you dump more stuff into them, and they do so such that adding elements to them is amortized constant time. If you haven't had the CS theory to know what that means, it means "very fast".

(I will omit std:: from now on.)

vector<int> v; // This declares a vector of ints named v. It starts off empty.
cout << v.size() << endl; // Prints "0".
v.push_back(10); // Adds the int 10 to the back of the vector.
v.push_back(50);
v.push_back(30);
cout << v.size() << endl; // Prints "3".
cout << v[1] << endl; // Prints "50".

Vectors are indexed starting from 0. Operator overloading is used to make std::vector, which is a template class, look like an array. This is why operator overloading is a Good Thing - it enables intuitive notation.

The STL embraces the [begin, end) concept completely. That is, when specifying a range, we use inclusive beginning and exclusive end. If v.size() is 137, then the range of valid indices to use is [0, 137), or [0, 136]. The bracket means inclusive, the parenthesis means exclusive. [begin, end) is very, VERY useful for a number of reasons, and we'll see that with iterators soon.

Now, vectors do NOT provide checked access via operator[], for performance reasons. While push_back will resize a vector automatically if necessary, you CAN'T just do:

vector<int> v;
v[10] = 137;

That will fuck up your memory. The STL doesn't protect you from memory errors by prohibiting such things at the cost of performance; instead, it merely enables you to avoid memory errors, as LONG as you use the correct style. The above is basically "asking" for it. If you need checked access, use .at(), which will throw an exception if you try to access out of range locations.

Thanks to templates, you can have a vector of basically any class that has reasonable value semantics.

vector<int> a;
vector<unsigned char> b;
vector<string> c;
vector<int *> d;
vector<vector<int> > e; // Note space

The 2003 C++ Standard is slightly bogus with respect to syntax. The space above is necessary. vector<vector<int>> is parsed as having a right shift operator. This will be fixed in the next C++ Standard.

Vectors of vectors are a handy way to have multidimensional matrices.

Now, what constructors does vector have? The default vector constructor creates an empty vector, where v.size() is 0. You also have:

vector<string> v(10);

This creates a vector of 10 strings, all default-initialized (string's default ctor is to ""). Then there's:

vector<unsigned char> v(10, 'z');

This creates a vector of 10 unsigned chars, all initialized to 'z'.

deque and list look a lot and somewhat like vector, respectively, but are optimized for different things. deque supports fast insertions and removals at both its front and back, while still retaining the random access of []. (It does so by a very complex implementation.) list supports fast insertions and removals ANYWHERE, but at the cost of random access. std::list is a doubly linked list.
 
Let's work with vector for right now. vector guarantees contiguous element storage, so you can actually pass a vector to something that expects a C array, like so. Where you would pass in the array or a pointer to the first element, instead you pass in:

&v[0]

That's the address of the first element.

That was a (very) brief container overview. Now, what about iterators?

Iterators are a generalization of pointers. You can think of them as being objects of class type with the proper operator overloads to make them look a lot like pointers. (In fact, actual pointers can BE iterators.) Now, vector has two kinds of iterators:

vector<T>::iterator
vector<T>::const_iterator

Every kind of vector - vector<int>, vector<string>, etc. has its own kind of iterator - that's what the T above means.

vector<T>::iterator is an "iterator to non-const". It can be used to modify the element it points to. The analogous raw pointer is T *.

vector<T>::const_iterator is an "iterator to const". It may not be used to modify the element it points to. The analogous raw pointer is const T *.

How do you get an iterator to the first element in a vector? v.begin(). Of course, if v is empty, you MUST NOT dereference this iterator.

How do you get an iterator to one past the last element in a vector? v.end(). Remember [begin, end) now.

So, how can we use iterators? We can step through a vector, like so:

Code:
for (vector<int>::const_iterator i = v.begin(); i != v.end(); ++i) {
    cout << *i << endl;
}

That steps through a vector and prints every element.

Note: I use const_iterator because I am not modifying elements. Using iterator would be needlessly dangerous.

Note: I compare against v.end() with !=. In general, use this for iterators, and never <.

Note: I preincrement the iterator. This is because in general, iterators may be of class type, and postincrement can be inefficient with objects of class type. For style reasons, use preincrement for everything, including builtins, unless you specifically need postincrement. (I myself am now transitioning to this style.)

Now, the actual type of vector<T>::iterator is implementation-specific. In practice, it OFTEN turns out to be just T *, but you should NOT rely on this ever. When compiling a debugging build, iterator may turn into an object of class type.

So, we've given what is usually a T * a fancy name, you say. What does that buy us?

Consider std::list. This is a doubly linked list. Basically everyone has written a linked list before, and is reasonably familiar with how to traverse one. You have to step across pointers, remember? Well, with the iterator concept, that's all encapsulated. How do you print every element of a std::list?

Code:
for (list<int>::const_iterator i = l.begin(); i != l.end(); ++i) {
    cout << *i << endl;
}

It looks exactly the same! Here, i is definitely of class type, and preincrement and deference have been overloaded to do the proper listy things. You don't have to think about what's actually going on inside the std::list (or worse, std::deque) yourself - you just frob the iterators.

Now, what do STL algorithms do with iterators?
 
Why couldn't you teach here?

edit: I'm assuming that you're a prof or something along those lines. (Given the Caltech thing).
 
One of the simplest (ha) STL algorithms is the one used to nuke elements from a container.

Suppose you have a vector<int> and wish to eliminate all occurrences of "15" in it.

The proper STL algorithm to use is std::remove().

The Standard gives its prototype as:

template<class ForwardIterator, class T>
ForwardIterator remove(ForwardIterator first, ForwardIterator last,
const T& value);

This says, remove takes a "ForwardIterator" to the beginning of the sequence you want to operate on, and one to the end of the sequence (remember we are using [begin, end) here), and then the value you want to remove.

What's a ForwardIterator? I didn't get into the different kinds of iterators above, but a vector's iterator is good enough for std::remove.

remove() returns an iterator to "the end of the resulting range", quoth the Standard.

But what does it DO?

I'll tell you what it DOESN'T do - it sure doesn't nuke elements from the vector. That's because STL algorithms don't know anything about containers - they can't resize them, which is what you WANT to do if you're nuking stuff from a vector. In order to actually nuke values at the end of a vector, you need to use a vector member method called erase:

v.erase(SOME ITERATOR, v.end());

That will erase all values beginning at SOME ITERATOR to the end of the vector. The vector is resized and those values are totally gone.

But the values we want to nuke aren't contiguously grouped at the end of the vector so far as we know - they're scatted throughout, and all equal to 15.

What remove does is move all of the values you want to preserve to the front of the vector. Garbage (NOT necessarily the values you want to nuke!) is left at the end of the vector, and remove tells you where that garbage begins. You can then erase it.

What's this garbage business? Think about how you'd write std::remove if you couldn't know anything about the container, only about beginning and ending iterators. The natural implementation (and the one the Standardization Committee had in mind when they wrote the std::remove interface) is a single pass over the vector with two iterators. They both begin at the start of the vector. You then step through the vector with one iterator - when you see a value you want to keep, you have the other iterator write it and step forward one. When you see a value you want to nuke, you do nothing with the other iterator. Thus, you end up writing all of the values you want to keep in their original order at the front of the vector. The stuff left at the end is the original contents of the end - maybe including duplicate values of stuff that got moved to the beginning, maybe including some of the values you wanted to nuke.

The Standard does NOT guarantee that this is what happens - I provide it only so that the "garbage" makes sense.

So, with this in mind, how do we nuke all values equal to 15 from our vector?

remove(v.begin(), v.end(), 15) will perform the operation described above, returning an iterator to the beginning of the garbage. We then want to erase starting at that point and continuing on to the end. The final result is:

v.erase(remove(v.begin(), v.end(), 15), v.end());

And that's it! Imagine how to do that with shitty C arrays - you'd have to write the remove loop yourself, and then you'd have to realloc the damn thing, and then check for success, blah blah yak yak.

Note that erase is a member method, and to put v.end() as the second argument to erase. erase also has a single-iterator form, and if you forget to put the v.end() there, it will silently compile and do the Wrong Thing.

I ALWAYS make that mistake (it's so easy to forget to put it there). So, in my personal library libnuwen I wrote a templated function called nuke(). I made sure to get it right THERE, and now I can use nuke() anywhere without worrying about getting something wrong.

remove() is very general - you can pass it ANY range, not just the v.begin(), v.end() range. This is a Good Thing - Standard algorithms should be as widely useful as possible. But most of the time, you just want to apply something to a whole container at once. The Standard does not provide container-based algorithms. I happen to disagree mildly with this decision, but it does keep the size of the library down. And you can always write your own wrappers. My nuke() is container-based: when I want to nuke all 15's from a vector, I just do:

nuke(v, 15);

Easy, eh? As I said, the STL is a library that lets you build machinery on top of it. It doesn't try to do everything for you - instead, it gives you the tools to create powerful, fast algorithms yourself simply and clearly.

The process described above is known as the "erase-remove idiom".

Look at that - all of that text, and just to explain the very utter basics of sequence containers, iterators, and the erase-remove idiom. I didn't get into the Boost Lambda Library, transform(), associative containers, or any of that good stuff at all!

That'll have to wait for the next installment. :)
 
[fat-tony]
> Why couldn't you teach here?

Accident of timing. See, I was born in 1983, and didn't bother to learn C until 2000, and C++ at the end of 2001. That put me at the best time to learn C++, if you ask me - right after all of the Standardization had settled down and gcc 3.x came out. I didn't have to unlearn old, shitty C++. Had I been born earlier, I'd probably have been a shitty C++ programmer. ^_^

Or, if you like another explanation: There just ain't no justice.
 
[fat-tony]
> edit: I'm assuming that you're a prof or something along those
> lines. (Given the Caltech thing).

Nope, just a senior CS major. I just happen to SOUND like a prof, even though I'm really an undergraduate. ;-)
 
Cool beans. I started learning C at way too young of an age, and too informally :). I'm sure I've picked up some bad practices along the way.

Hmm... Not really the appropriate place for this... Carry on with the tutorials :D
 
Lambda previews:

Doubling all of the ints in a vector:

for_each(v.begin(), v.end(), _1 *= 2);

A priority queue of pointers-to-Node that is sorted such that the smallest Nodes are at the .top() of the container:

priority_queue<Node *, vector<Node *>, boost::function<bool (Node *, Node *)> > pq(*_1 > *_2);

Assigning the value 20 to each .x coordinate in a vector of Points:

for_each(v.begin(), v.end(), (&_1)->*&Point::x = 20);

Nuking all even elements from a vector:

v.erase(remove_if(v.begin(), v.end(), _1 % 2 == 0), v.end());
 
The Boost Lambda Library

The Boost Lambda Library, documented here:

http://www.boost.org/libs/lambda/doc/index.html

is an amazing new development in generics, and (if you ask me) the first successful integration of functional programming with a traditionally imperative language.

"Functional" langagues, roughly speaking, are those in which functions are first class data types that can be manipulated with the same ease as integers. Examples include LISP, Scheme, and Haskell. The functional programming philosophy is not all-encompassing, and languages based solely on it don't work well. However, it has its merits, and can lead to some very elegant, simple code.

In C++, functions aren't first class data types. The most you can get from a function is a pointer to that function. However, classes do act like first class data types, and are generally "as good as" the real thing. Witness smart pointers (in place of raw pointers), vectors (in place of arrays), strings (in place of null-terminated strings), and so forth. So, a common idiom in C++ arose: that of the functor.

A functor is anything with operator() that behaves like a function. Functors abstract the concept of functions and add state, since the duty of an object is to hold state and provide services. Normal functions should not hold state (static data is evil). The Standard Template Library algorithms often take functors to transform, compare, or inspect items.

The Boost Lambda Library allows you to define small, unnamed functors directly at the point of call of an STL algorithm. They let you avoid tedious boilerplate code and handcrafted loops.

Imagine this function (functors are a generalization of functions; functions can be passed to STL algorithms no problemo):

bool isEven(int x) { return x % 2 == 0; }

Then you can imagine doing,

v.erase(remove(v.begin(), v.end(), isEven), v.end());

Instead, with Boost, you can write:

v.erase(remove(v.begin(), v.end(), _1 % 2 == 0), v.end());

An object named _1 of a special type overloads most C++ operators, and it can produce new functors that do what you want.

_1 itself is the identity functor - it takes an argument and returns it. If you say _1 + 50, you get a functor that adds 50 to its argument. For example, (_1 + 50)(200) returns 250.

You can create lambda functions up to ternary, using the objects _2 and _3. (If you think those names are ugly, you can make your own, such as X, Y, and Z.) For example, you could dot-product two sequences like this:

transform(v.begin(), v.end(), w.begin(), back_inserter(x), _1 * _2);

Or if you have two vectors of int * and want to dot-product the pointed-to things, use *_1 * *_2. Simple!

My favorites in the preview above are the priority_queue (because it occured in real code) and the third example, because it's tricky.

Suppose you want to assign the value 20 to each .x coordinate in a vector of Points. Well, you could write a loop, but that would suck.

You'd like to be able to say, _1.x = 20. But the dot operator isn't overloadable in C++. Okay, let's try -> as that's certainly overloadable. (&_1)->x = 20 won't work EITHER, because of requirements on the return type of operator-> - smart pointers can overload it, but lambda functors can't.

Well, there's a little-known C++ operator, ->* which takes a pointer to an object on its LHS and a pointer to a member on its RHS. It so happens that you can have pointers to data members, which is what we want. The pointer to member x is written as &Point::x. So, we get:

(&_1)->*&Point::x = 20

That unwieldy thing WILL work. I argue that, though cluttered, it's more elegant than a handrolled loop, and that if you use it enough, it'll become idiomatic.
 
Here are some of the basic things that STL showed me or suggested to me that really helped me. (STL correct me if needed. This is kind of a reminder for you to cover the super basic stuff)


return 0
It is not needed at the end of C++ program.

My take for the reason on that is, if the program successfully executes all code and has nothing left to do, the program will exit successfully. The return 0 is implied and compilers are built with that in mind.

Speaking of that, instead of return 0 and return 1, you can use exit(0) and exit(1). exit() lives in <cstdlib> and if you use exit(), you should include <cstdlib> even if you can compile without it..

However, for visual representation, exit(EXIT_SUCCESS) and exit(EXIT_FAILURE) are suggested. You have to decide which one to use. For example, if there is an error opening a file, that would be a FAILURE, but if you passed an argument like --help along to the program and it displayed the usage, that would be a SUCCESS.

std::vector
As already said, std::vector helps big time. I made a string tokenizer with a bunch of find() this and find() that. Using <vector>string really helped. Of course using Boost Regex really put the vector method to shame.

Define variables near where they are first used and not at the top like in C

Indentation is important.
Tabs are nasty. I have my editor set to indent 4 spaces when I hit tab. Saves you lots of trouble; especially when posting code on forums.

use non-member getline() instead of cin; even for integer values etc.

find() does not return -1 if it can't find what it's looking for. it returns std::string::npos

reading/writing files
<fstream>

Do not do the following to open a file for reading.
*
fstream input;
input.open(filename.c_str(), fstream::in);
*

Use ifstream input(filename.c_str()); instead. Just one line. It's simpler. Plus I've corrupted input files by trying to do it with fstream.

*
Do not use use fstream() to open a file for writing either.
*

Use ofstream file(outputname.c_str()); for that.

Also, do not use file.close(). You do not need to close the file in most cases.

int main(int argc, char *argv[])
Although you can, it is not suggested to rename argc and argv to your liking.

if (argc > 1 && argc < 3)
I was so guilty of that. As STL said, "Normal humans write this as: argc == 2".

There's more, but that's enough for now.

@STL

It would be good to list some of the optimization flags that you can use and furthermore, which ones you might not want to use etc. Plus things like -mwindows and using strip etc.
 
Originally posted by Shadow2531

Also, do not use file.close(). You do not need to close the file in most cases.

I personally think that you're better off closing the file after use, especially when writing. Until you close a file, you can't be guaranteed that it has been written to disk. Also, leaving it open uses system resources that you aren't using.
 
doesnt file.close() make sure that any buffered data is written to disk? just like file.flush()
 
Originally posted by fat-tony
I personally think that you're better off closing the file after use, especially when writing. Until you close a file, you can't be guaranteed that it has been written to disk. Also, leaving it open uses system resources that you aren't using.

I may have misinterpreted what STL suggested. He may have suggested to not use file.close() with some of the small apps I was working on. They would just grab the contents of a csv file, modify them and write it to a new file. Then the program would close. When the program closed, it closed the file too. There was no reason to close the file

However, for large apps that grab info from a file, and still have a lot of things to do, but don't need the info from that file anymore, might need close() to save resources.

Of course I'm not sure on the reason it was suggested to not use close, but apps seemed to work fine without it.

Here's the reason I was given.

"Resource acquisition is initialization. That is to say, do not use .open() and .close() with streams."
STL
 
Originally posted by Shadow2531
However, for large apps that grab info from a file, and still have a lot of things to do, but don't need the info from that file anymore, might need close() to save resources.

This can be almost always be easily solved by properly structuring you code. The file doesn't close when the program ends, it closes when the destructor for the stream is called. He's not suggesting that you keep every file you open open for the entire run of the program, but that you don't close them yourself, let the class maintain itself. Use the constructor to open it, and know that the destructor to flush and close it.
 
Originally posted by FuzzyDonkey
This can be almost always be easily solved by properly structuring you code. The file doesn't close when the program ends, it closes when the destructor for the stream is called. He's not suggesting that you keep every file you open open for the entire run of the program, but that you don't close them yourself, let the class maintain itself. Use the constructor to open it, and know that the destructor to flush and close it.


Ahhhhh.... That makes sense.
 
[Shadow2531]
> It would be good to list some of the optimization flags that
> you can use and furthermore, which ones you might not want to
> use etc. Plus things like -mwindows and using strip etc.

This is compiler-specific.

[FuzzyDonkey]
> Use the constructor to open it, and know that the destructor
> to flush and close it.

Correct. The destructor will flush and close the file. An explicit .close() is unnecessary unless you need to close the file before the end of the block (which is highly unusual).

God, this forum [software] sucks even more than before.
 
Can you be more specific as to what part is the constructor and what part is the destructor in the following program? I assume ofstream and the close of main respectively, but again not sure.


Code:
#include <iostream>
#include <string>
#include <fstream>
#include <cstdlib>

using namespace std;

int main() {
    string filename = "example.txt";
    ofstream example(filename.c_str());
    if (!example) {
        cout << endl << "Error opening " << filename << endl;
        exit(EXIT_FAILURE);
    }
    example << "Some text" << endl;
}

Thanks
 
[Shadow2531]
> Can you be more specific as to what part is the constructor and what part is the destructor

Constructors and destructors are /special member functions/. When working with Standard classes, you don't see their definitions in your own code. Your own code invokes the constructors and destructors of the Standard classes (sometimes explicitly, sometimes implicitly).

When you write your own objects, then you can write your own constructors and destructors, or have the compiler generate them for you.

Consider the following example of an "instrumented class".

Code:
#include <iostream>
using namespace std;

class Instrumented {
public:
    Instrumented(const string& s) : name(s) { // Constructor
        cout << "Constructing an Instrumented named \"" << name << "\"." << endl;
    }

    Instrumented(const Instrumented& rhs) : name("Copy of " + rhs.name) { // Copy constructor
        cout << "Copy constructing the Instrumented named \"" << rhs.name << "\"." << endl;
    }

    Instrumented& operator=(const Instrumented& rhs) { // Assignment operator
        cout << "The Instrumented formerly known as \"" << name << "\" is now known as \"" << rhs.name << "\"." << endl;
        name = rhs.name;
        return *this;
    }

    void speak() const {
        cout << "My name is \"" << name << "\"." << endl;
    }

    ~Instrumented() { // Destructor
        cout << "Destructing the Instrumented named \"" << name << "\"." << endl;
    }

private:
    string name;
};

void takes_by_value(Instrumented i) {
    i.speak();
}

void takes_by_const_reference(const Instrumented& i) {
    i.speak();
}

void takes_by_const_ptr_to_const(const Instrumented * const p) {
    p->speak();
}

int main() {
    Instrumented dark_lord("Warren Spector");
    Instrumented lord_of_light("Roger Zelazny");
    lord_of_light = dark_lord;
    Instrumented ryan_myers(dark_lord);

    cout << "Before artificial block." << endl;

    {
        cout << "Inside artificial block." << endl;

        Instrumented cat("Bucky");

        takes_by_value(cat);
        takes_by_const_reference(cat);
        takes_by_const_ptr_to_const(&cat);

        cout << "Leaving artificial block." << endl;
    }

    cout << "Outside artificial block. End of main() imminent. Repent, sinners." << endl;
}

If you run this, you will get:

[5/31/2004 Mon 9:31.30 PM stl@nuwen ~] [1647 1740 3387]
> g++ -Wall -W foo.cc -o foo

[5/31/2004 Mon 9:31.34 PM stl@nuwen ~] [1647 1740 3387]
> foo
Constructing an Instrumented named "Warren Spector".
Constructing an Instrumented named "Roger Zelazny".
The Instrumented formerly known as "Roger Zelazny" is now known as "Warren Spector".
Copy constructing the Instrumented named "Warren Spector".
Before artificial block.
Inside artificial block.
Constructing an Instrumented named "Bucky".
Copy constructing the Instrumented named "Bucky".
My name is "Copy of Bucky".
Destructing the Instrumented named "Copy of Bucky".
My name is "Bucky".
My name is "Bucky".
Leaving artificial block.
Destructing the Instrumented named "Bucky".
Outside artificial block. End of main() imminent. Repent, sinners.
Destructing the Instrumented named "Copy of Warren Spector".
Destructing the Instrumented named "Warren Spector".
Destructing the Instrumented named "Warren Spector".

[5/31/2004 Mon 9:31.35 PM stl@nuwen ~] [1647 1740 3387]
>

What's going on there?

Constructing an Instrumented named "Warren Spector". <== dark_lord constructed
Constructing an Instrumented named "Roger Zelazny". <== lord_of_light constructed
The Instrumented formerly known as "Roger Zelazny" is now known as "Warren Spector". <== lord_of_light = dark_lord
Copy constructing the Instrumented named "Warren Spector". <== ryan_myers copy constructed from dark_lord
Before artificial block.
Inside artificial block.
Constructing an Instrumented named "Bucky".
Copy constructing the Instrumented named "Bucky". <== Calling takes_by_value() will copy construct cat
My name is "Copy of Bucky". <== takes_by_value() works on the copy, not on the original
Destructing the Instrumented named "Copy of Bucky". <== The copy is destructed when takes_by_value() finishes
My name is "Bucky". <== Taking by const reference does not copy - this works on the original
My name is "Bucky". <== Same for taking by [const] pointer to const
Leaving artificial block.
Destructing the Instrumented named "Bucky". <== cat dies at the end of his artifical block
Outside artificial block. End of main() imminent. Repent, sinners.
Destructing the Instrumented named "Copy of Warren Spector". <== The objects constructed are destructed in reverse order
Destructing the Instrumented named "Warren Spector".
Destructing the Instrumented named "Warren Spector".
 
Thanks.

Does anyone have general concepts/areas they'd like me to cover? I have a separate compilation tutorial I could write up.
 
To follow up on something Shadow2531 mentioned:

Delay variable definitions for as long as possible.

In C (the 1989 Standard everyone still uses), variable definitions have to occur at the start of a block, before any statements. C++ does not suffer this fascist restriction (nor does the 1999 C Standard which is not in use yet).

"But," you say, "I like grouping all of my variable definitions at the top of my blocks, because that way I can see at once glance all of the variables which are used."

Cast aside that dubious reasoning. When you say that, you're really saying, "But I did it that way in C, why do I have to change?"

There are three reasons (that I can think of at 3 AM) why putting definitions closest to the "point of use" is good C++ style:

1. Ease of reading. If definitions are kept close to the point of use, then it will be easier to look up the definition when the point of use confuses you. (Remember, Hungarian notation is bad. Variable type and initializer information belongs in the definition, not as baggage in the variable's name. So you want the declaration to always be nearby.)

int foobar;

// War And Peace, which does not need foobar, follows

foobar = zlort(); // Can you even remember what foobar's purpose is here?

2. Safety. If definitions are kept close to the point of use, nothing can screw with the variable's state in the meantime!

int foobar = zlort();

// Twenty pages of code which doesn't need foobar but can screw it up

fxn(foobar); // Did foobar get screwed up?

3. Efficiency. Objects are constructed when their definitions are passed over. If a function exits at a point above some variable definitions, those variables are never constructed and destructed! You save useless work.

Thus, you should always strive to put variable definitions as close to the point of use when possible. That also means, use C++ for() style rather than C for() style except when there is an important reason not to (in highly specific situations you need access to the loop index after the loop exits).

This issue is closely related to Resource Acquisition Is Initialization, which - among other things - involves always /initializing/ objects at their definitions rather than doing it later.

That is,

int x = 10;
int y = zlort();
ifstream f("oinkity.mlar");

Rather than:
int x;
int y;
ifstream f;
// Blah blah blah
x = 10;
y = zlort();
f.open("oinkity.mlar");

The three reasons mentioned above apply here.

1. Ease of reading. The initializer conceptually belongs with the definition of the variable, so put it there!

2. Safety. If all variables are initialized when defined, then they are never in a dangerous uninitialized state (builtins) or a useless default constructed state (user defined types).

3. Efficiency. Default construction followed by assignment, or followed by explict resource acquisition (e.g. .open() and friends) is obviously less efficient than direct construction.
 
I should probably mention that while the 1998 and 2003 C++ Standards cost $18 each in PDF form, you can now access the Working Paper (which will become the next C++ Standard) freely:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1638.pdf

The site is slow, but it will load eventually. To make rocket go now:

http://stl.nuwen.net/n1638.pdf

Note that the Working Paper is not an official Standard. However, if you're too cheap to buy a copy of the Standard, looking at the Working Paper is probably better than looking at the pre-1998 draft of the Standard which is floating around on the Internet.

(The crummy thing about the Working Paper is that it has no nice bookmarks like the real Standard does, so navigating around it is hard.)

--

I recently learned how to use Boost Regex Token Iterators. Maybe I'll write a tutorial about Boost Regex...
 
Back
Top