Rich Grise wrote:
I think I've finally found a tutorial that can get me started:
http://www.zib.de/Visual/people/muel.../tutorial.html
Have you written a "hello world" program yet? And always learn C++ from more
than two tutorials, at the same time. Try /Accelerated C++/ by Koenig and
Moo.
So, I see this in the tutorial, which is a construct I've seen in
the NG (he's talking about linked lists):
--------------
list_handle_t<Apple> list1; /* a list of apples */
list_handle_t<Car> list2; /* a list of cars */
--------------
So far, he's been using pseudocode, fair enough, I first learned to
copy-n-paste C hundreds of years ago. But I wonder - in this particular
example, is this guy speaking "real C++"?
Unfortunately, yes. They are templates. (But the comments could use //
instead of /* */.)
The following dissertation intends to help people read C++, not write new
code. You would do me a major favor if you read the following and told me
where (if anywhere) you get lost.
Bjarne Stroustrup invented C++ to provide the popular but low-level C
language with the keywords virtual, template and operator. Those enable
Object Oriented techniques with minimal runtime overhead. C is "portable
assembler", and C++ is "portable OO assembler".
C++ in a Nutshell
C++ is a statically typed language. It has no latent "Object" base class to
supply default behaviors to all objects. C++ supports Object Oriented
techniques, but not everything in C++ is an object.
All C++ entities have a declaration and a definition. C++ uses a compiling
and linking system that compiles many declarations and a few definitions
together into one "object code file". Then many of these link together to
form a complete program.
Only source statements compiled below a declaration can use its identifiers.
Developers can declare and define classes and functions simultaneously (the
preferred way), or they can move declarations to separate files. This Case
Study will show tests followed by the code that passes them, but that code,
or its declarations, must in source appear above their tests.
C++ supports very large Object Oriented applications when modules typesafely
compile remote modules' interface declarations, without the burden of
recompiling their implementation definitions.
Language Law: C++ supports both methods and modules, but has no single
explicit keyword for either. Programmers assemble methods and modules from
primitive facilities. Roughly speaking, a method is a virtual class member
function, and a module is a translation unit, typically based on compiling
one .cpp file.
The command to import a module's declarations is #include, and the command
to import an identifier from that module is using. The Hello World project
for C++ is:
#include <iostream> // copy in declarations, and some definitions
using std::cout; // meaning Console OUTput
using std::endl; // meaning END of Line
int main()
{
cout << "Hello World!" << endl;
return 0;
}
The function main() returns a 0, which tells the environment that this
program ran without errors.
The number 0 has a weak type, usually integer. All C++ functions declare
their return type, or they declare void to return nothing.
In my Uncommon Style Guide, functions' return types are less important than
the functions' names. Indenting the return values, like this...
int
main()
{
....
}
....cleans the left column, so scanning it reveals only function names. This
style becomes very easy to read - especially as function types get long.
Applying the Object Method Refactor to the Hello World application yields
this class:
using std::ostream;
class
WorldGreeter
{
public:
void
greet(ostream &out)
{
out << "Hello World!" << endl;
}
};
int
main()
{
WorldGreeter aGreeter;
aGreeter.greet(cout);
return 0;
}
The console stream object, cout, passes into greet() by reference. greet()
takes a reference (&) to an ostream, which is one of cout's classes' base
classes. ostream and many classes derived from it use << to insert data into
their streams.
This stream insertion concept makes the following chapters easier to
understand. C++ permits operator overloading, meaning programmers specify
what some operators do. The operator <<, applied to integers, shifts their
bits, so 2 << 3 == 16.
Shifting objects makes no sense, so << is free to overload for stream
objects. Refactoring our "Hello World" project, one more time, illustrates
this principle:
class
WorldGreeter
{
public:
void
greet(ostream &out) const
{
out << "Hello World!";
}
};
ostream &
operator<<(ostream &out, WorldGreeter const &aGreeter)
{
aGreeter.greet(out);
return out;
}
int
main()
{
WorldGreeter aGreeter;
cout << aGreeter << endl;
return 0;
}
The ability to insert any object, as text, into a stream, is more than
syntactic sugar. When templates and macros call operator<<, they can use any
argument type that has such an operator. The Standard Library defines
operator<< for all the primitive data types-int, double, std::string,
etc.-and programmers define one for each object they need. Note the above
operator<< is part of WorldGreeter's interface, but is not a member of the
WorldGreeter class. C++ types work in mysterious ways.
In C++, classes are not objects. This makes passing a class into a method,
as a parameter, problematic. C++ compiles all class details into various
opcodes, wherever they occur, so a running C++ program has no single entity
to refer to as a class.
C++ programs use templates to merge types. Each template instantiation
expands a class-like definition into a set of classes, each varying by some
type or integer.
This example upgrades our WorldGreeter example one more useless time. This
version converts WorldGreeter into a template, parameterized by an integer
that sets how ecstatically the two instantiated classes, WorldGreeter<2> and
WorldGreeter<3>, greet the world:
template<int count>
class
WorldGreeter
{
public:
void
greet(ostream &out) const
{
for(int x = 0; x < count; ++x)
out << "Hello World! ";
}
};
ostream &
operator<<(ostream &out, WorldGreeter<2> const &aGreeter)
{
aGreeter.greet(out);
return out;
}
ostream &
operator<<(ostream &out, WorldGreeter<3> const &aGreeter)
{
aGreeter.greet(out);
return out;
}
int
main()
{
WorldGreeter<2> aGreeter2;
WorldGreeter<3> aGreeter3;
cout << aGreeter2 << aGreeter3 << endl;
return 0;
}
When templates parameterize based on types, they can create new classes that
specialize to manage any of a range of details for each type. For example,
the C++ Standard Library provides a template called std::basic_string<>.
Supplying this with a character type provides string types of any needed
width:
namespace std {
typedef basic_string<char> string; // 8-bits
typedef basic_string<wchar_t> wstring; // 16-bits
}
The keyword namespace is one of the language primitives that collude to
create modules. Identifiers from the C++ Standard Library often begin with
std::, to distinguish their source module. A namespace is a prefix (std)
that must precede all uses of identifiers declared within their scope
(string, wstring). The scope operator, ::, links them.
The keyword typedef converts a type declaration into a single name. Here, it
instantiates templates into their target classes. The sample creates the
class std::string, to contain 8-bit character strings, and the class
std::wstring, to contain 16-bit character strings.
To avoid writing std:: all over the place, insert a namespace's identifiers
into your scope with a few using declarations:
#include <string>
#include <sstream>
#include <iostream>
using std::string;
using std::stringstream;
using std::cout; // console output stream
using std::endl; // line feed object
C++ macros provide some important features above the level of the actual
language. Here's a macro, using some of those stream things:
#define db(x_) cout << (x_) << endl
Macros perform simple text substitutions directly on raw C++ source, before
the type-sensitive phase of compilation. Put another way, if you can't think
how to do something directly, you can often write a macro to do it. (Those
of you who know C++ well enough to abhor that advice are requested to
attempt the following techniques using any other C++ facilities.)
Below that macro, if you write db(5), you get cout << (5) << endl. A rote
text substitution, without awareness of 5's meaning. It's just raw text. The
cout << (5) then inserts 5, typesafely, into the console stream, so you can
see it on a command prompt. The << endl part delivers a linefeed, and
flushes that console stream's buffer.
So far, a small templated function could have performed the same feat. Use
macros for the stringerizer operator, #:
#define db(x_) cout << #x_ ": " << (x_) << endl
"db" stands for debug. If q == 5, and if q is suspicious, we can trace q's
name and value to the console using db(q). That outputs "q: 5\n". This macro
makes our trace statement easy to write. The equivalent printf() statement
(if q were only an integer) would be printf("q: %i\n", q). Our statement
works for any type that supports an operator<<, and it outputs the name of q
automatically, using #x_.
Suppose a long program run emitted a strange message, complaining about some
obscure variable named q. Rapidly finding the line of code containing that
db(q) call relieves stress:
#define db(x_) do { \
cout << __FILE__ << "(" << __LINE__ << ") : " \
#x_ " = " << x_ << endl; } while (false)
That emits, roughly, "C:\path\source.cpp(99) : q = 5\n". All from one easily
written db(q) call.
The do and while(false) keywords force the statement containing db(q) to
finish with a healthy ; semicolon. Macros need techniques, like extra
parentheses, to avoid clashing with raw C++ statements.
On the ends of broken lines, two reverse solidi, \, join everything into one
long macro. The __FILE__ and __LINE__ symbols are magic constants, defined
by the C languages, which expand into the current name of the source file
and line number.
When a program encounters syntax errors, faults, or trace statements, the
same keystroke must navigate to any of them.
Visual Studio surfs to errors using <F8>: Go To Output Window Next Location.
The Windows SDK function to write text into the output panel, for this
feature to read it and surf to an error, is OutputDebugString().
Putting them all together yields this killer trace macro:
#define db(x_) do { std::stringstream z; \
z << __FILE__ << "(" << __LINE__ << ") : " \
#x_ " = " << x_ << endl; \
cout << z.str() << std::flush; \
OutputDebugStringA(z.str().c_str()); \
} while (false)
That takes any argument, including expressions, which support operator<<. We
will return to these techniques while exploring more Fault Navigation issues
in C++.
db(q) pushes "C:\path\source.cpp(99) : q = 5\n" into the Output Debug panel.
<F8> parses the file name and line number and navigates your editor directly
to the line containing the db(q).
Those are major wins. Tracing with db() is very low cost for very high
feedback.
C++ has flaws. But those of you inclined to dismiss it entirely are invited
to write db(), with all these features, in your favorite language.
--
Phlip
http://industrialxp.org/community/bi...UserInterfaces