home *** CD-ROM | disk | FTP | other *** search
Text File | 2000-03-01 | 52.4 KB | 1,528 lines |
- Path: senator-bedfellow.mit.edu!bloom-beacon.mit.edu!howland.erols.net!novia!nntp3.cerf.net!nntp2.cerf.net!news.cerf.net!not-for-mail
- From: mpcline@nic.cerf.net (Marshall Cline)
- Newsgroups: comp.lang.c++,comp.answers,news.answers,alt.comp.lang.learn.c-c++
- Subject: C++ FAQ (part 4 of 10)
- Followup-To: comp.lang.c++
- Date: 29 Feb 2000 20:06:42 GMT
- Organization: ATT Cerfnet
- Lines: 1507
- Approved: news-answers-request@mit.edu
- Distribution: world
- Expires: +1 month
- Message-ID: <89h8si$bts$1@news.cerf.net>
- Reply-To: cline@parashift.com (Marshall Cline)
- NNTP-Posting-Host: nic1.san.cerf.net
- X-Trace: news.cerf.net 951854802 12220 192.215.81.88 (29 Feb 2000 20:06:42 GMT)
- X-Complaints-To: abuse@cerf.net
- NNTP-Posting-Date: 29 Feb 2000 20:06:42 GMT
- Summary: Please read this before posting to comp.lang.c++
- Xref: senator-bedfellow.mit.edu comp.lang.c++:453822 comp.answers:39862 news.answers:178221 alt.comp.lang.learn.c-c++:40800
-
- Archive-name: C++-faq/part4
- Posting-Frequency: monthly
- Last-modified: Feb 29, 2000
- URL: http://marshall-cline.home.att.net/cpp-faq-lite/
-
- AUTHOR: Marshall Cline / cline@parashift.com / 972-931-9470
-
- COPYRIGHT: This posting is part of "C++ FAQ Lite." The entire "C++ FAQ Lite"
- document is Copyright(C)1991-2000 Marshall Cline, Ph.D., cline@parashift.com.
- All rights reserved. Copying is permitted only under designated situations.
- For details, see section [1].
-
- NO WARRANTY: THIS WORK IS PROVIDED ON AN "AS IS" BASIS. THE AUTHOR PROVIDES NO
- WARRANTY WHATSOEVER, EITHER EXPRESS OR IMPLIED, REGARDING THE WORK, INCLUDING
- WARRANTIES WITH RESPECT TO ITS MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR
- PURPOSE.
-
- C++-FAQ-Lite != C++-FAQ-Book: This document, C++ FAQ Lite, is not the same as
- the C++ FAQ Book. The book (C++ FAQs, Cline and Lomow, Addison-Wesley) is 500%
- larger than this document, and is available in bookstores. For details, see
- section [3].
-
- ==============================================================================
-
- SECTION [10]: Constructors
-
-
- [10.1] What's the deal with constructors?
-
- Constructors build objects from dust.
-
- Constructors are like "init functions". They turn a pile of arbitrary bits
- into a living object. Minimally they initialize internally used fields. They
- may also allocate resources (memory, files, semaphores, sockets, etc).
-
- "ctor" is a typical abbreviation for constructor.
-
- ==============================================================================
-
- [10.2] Is there any difference between List x; and List x();?
-
- A big difference!
-
- Suppose that List is the name of some class. Then function f() declares a
- local List object called x:
-
- void f()
- {
- List x; // Local object named x (of class List)
- // ...
- }
-
- But function g() declares a function called x() that returns a List:
-
- void g()
- {
- List x(); // Function named x (that returns a List)
- // ...
- }
-
- ==============================================================================
-
- [10.3] How can I make a constructor call another constructor as a primitive?
-
- No way.
-
- Dragons be here: if you call another constructor, the compiler initializes a
- temporary local object; it does not initialize this object. You can combine
- both constructors by using a default parameter, or you can share their common
- code in a private init() member function.
-
- ==============================================================================
-
- [10.4] Is the default constructor for Fred always Fred::Fred()?
-
- No. A "default constructor" is a constructor that can be called with no
- arguments. Thus a constructor that takes no arguments is certainly a default
- constructor:
-
- class Fred {
- public:
- Fred(); // Default constructor: can be called with no args
- // ...
- };
-
- However it is possible (and even likely) that a default constructor can take
- arguments, provided they are given default values:
-
- class Fred {
- public:
- Fred(int i=3, int j=5); // Default constructor: can be called with no args
- // ...
- };
-
- ==============================================================================
-
- [10.5] Which constructor gets called when I create an array of Fred objects?
-
- Fred's default constructor[10.4] (except as discussed below).
-
- There is no way to tell the compiler to call a different constructor (except as
- discussed below). If your class Fred doesn't have a default constructor[10.4],
- attempting to create an array of Fred objects is trapped as an error at compile
- time.
-
- class Fred {
- public:
- Fred(int i, int j);
- // ... assume there is no default constructor[10.4] in class Fred ...
- };
-
- int main()
- {
- Fred a[10]; // ERROR: Fred doesn't have a default constructor
- Fred* p = new Fred[10]; // ERROR: Fred doesn't have a default constructor
- }
-
- However if you are creating an STL[32.1] vector<Fred> rather than an array of
- Fred (which you probably should be doing anyway since arrays are evil[21.5]),
- you don't have to have a default constructor in class Fred, since you can give
- the vector a Fred object to be used to initialize the elements:
-
- #include <vector>
- using namespace std;
-
- int main()
- {
- vector<Fred> a(10, Fred(5,7));
- // The 10 Fred objects in vector a will be initialized with Fred(5,7).
- // ...
- }
-
- Even though you ought to use a vector rather than an array, there are times
- when an array might be the right thing to do, and for those, there is the
- "explicit initialization of arrays" syntax. Here's how it looks:
-
- class Fred {
- public:
- Fred(int i, int j);
- // ... assume there is no default constructor[10.4] in class Fred ...
- };
-
- int main()
- {
- Fred a[10] = {
- Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7),
- Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7)
- };
-
- // The 10 Fred objects in array a will be initialized with Fred(5,7).
- // ...
- }
-
- Of course you don't have to do Fred(5,7) for every entry -- you can put in any
- numbers you want, even parameters or other variables. The point is that this
- syntax is (a) doable but (b) not as nice as the vector syntax. Remember this:
- arrays are evil[21.5] -- unless there is a compelling reason to use an array,
- use a vector instead.
-
- ==============================================================================
-
- [10.6] Should my constructors use "initialization lists" or "assignment"?
-
- Constructors should initialize all member objects in the initialization list.
-
- For example, this constructor initializes member object x_ using an
- initialization list: Fred::Fred() : x_(whatever) { }. From a performance
- perspective, it is important to note that the whatever expression doesn't
- automatically cause a separate object to be created and copied into x_: if the
- types are the same the result of ...whatever... will be constructed directly
- inside x_.
-
- In contrast the following constructor uses assignment:
- Fred::Fred() { x_ = whatever; }. In this case the expression whatever causes a
- separate, temporary object to be created, and this temporary object is passed
- into the x_ object's assignment operator, then is destructed at the ;. That's
- inefficient.
-
- There's another source of inefficiency as well: in the second (assignment)
- case, the object's default constructor (implicitly called before the
- constructor body's "{") might, for example, allocate some default amount of
- memory or open some default file. All this work could be for naught if the
- whatever expression and/or assignment operator causes the object to close that
- file and/or release that memory (e.g., if the default constructor didn't
- allocate a large enough pool of memory or if it opened the wrong file).
-
- Conclusion: All other things being equal, your code will run faster if you use
- initialization lists rather than assignment.
-
- ==============================================================================
-
- [10.7] Should you use the this pointer in the constructor? [NEW!]
-
- [Recently created with the help of Jim Hyslop (on 1/00).]
-
- Some people feel you should not use the this pointer in a constructor because
- the this object is not fully formed yet. However you can use this in the
- constructor (in the {body} and even in the initialization list[10.6]) if you
- are careful.
-
- Once you're in the {body} of the constructor, it's easy to imagine that you can
- use the this pointer since all the base class subobjects and the member objects
- will already have been fully constructed. However even there you must be
- careful. For example, if you call a virtual member function (or call some
- other function which turns around and calls a virtual member function) on the
- this object, you may not get what you want[23.1].
-
- But you are even allowed to use the this pointer in the constructor's
- initializer list[10.6], provided you are very careful that you don't touch any
- member objects or base class subobjects that have not yet been constructed.
- This requires a rather intimate knowledge of the order that things happen in a
- constructor -- you have been warned. The safest thing to do is store the value
- of the this pointer somewhere and use that pointer later.
-
- ==============================================================================
-
- [10.8] What is the "Named Constructor Idiom"?
-
- A technique that provides more intuitive and/or safer construction operations
- for users of your class.
-
- The problem is that constructors always have the same name as the class.
- Therefore the only way to differentiate between the various constructors of a
- class is by the parameter list. But if there are lots of constructors, the
- differences between the constructors becomes somewhat subtle and error prone.
-
- With the Named Constructor Idiom, you declare all the class's constructors in
- the private: or protected: sections, and you provide public static methods that
- return an object. These static methods are the so-called "Named Constructors."
- In general there is one such static method for each different way to construct
- an object.
-
- For example, suppose we are building a Point class that represents a position
- on the X-Y plane. Turns out there are two common ways to specify a 2-space
- coordinate: rectangular coordinates (X+Y), polar coordinates (Radius+Angle).
- (Don't worry if you can't remember these; the point isn't the particulars of
- coordinate systems; the point is that there are several ways to create a Point
- object). Unfortunately the parameters for these two coordinate systems are the
- same: two floats. This would create an ambiguity error in the overloaded
- constructors:
-
- class Point {
- public:
- Point(float x, float y); // Rectangular coordinates
- Point(float r, float a); // Polar coordinates (radius and angle)
- // ERROR: Overload is Ambiguous: Point::Point(float,float)
- };
-
- int main()
- {
- Point p = Point(5.7, 1.2); // Ambiguous: Which coordinate system?
- }
-
- One way to solve this ambiguity is to use the Named Constructor Idiom:
-
- #include <math.h> // To get sin() and cos()
-
- class Point {
- public:
- static Point rectangular(float x, float y); // Rectangular coord's
- static Point polar(float radius, float angle); // Polar coordinates
- // These static methods are the so-called "named constructors"
- // ...
- private:
- Point(float x, float y); // Rectangular coordinates
- float x_, y_;
- };
-
- inline Point::Point(float x, float y)
- : x_(x), y_(y) { }
-
- inline Point Point::rectangular(float x, float y)
- { return Point(x, y); }
-
- inline Point Point::polar(float radius, float angle)
- { return Point(radius*cos(angle), radius*sin(angle)); }
-
- Now the users of Point have a clear and unambiguous syntax for creating Points
- in either coordinate system:
-
- int main()
- {
- Point p1 = Point::rectangular(5.7, 1.2); // Obviously rectangular
- Point p2 = Point::polar(5.7, 1.2); // Obviously polar
- }
-
- Make sure your constructors are in the protected: section if you expect Fred to
- have derived classes.
-
- The Named Constructor Idiom can also be used to make sure your objects are
- always created via new[16.19].
-
- ==============================================================================
-
- [10.9] Why can't I initialize my static member data in my constructor's
- initialization list?
-
- Because you must explicitly define your class's static data members.
-
- Fred.h:
-
- class Fred {
- public:
- Fred();
- // ...
- private:
- int i_;
- static int j_;
- };
-
- Fred.cpp (or Fred.C or whatever):
-
- Fred::Fred()
- : i_(10) // OK: you can (and should) initialize member data this way
- j_(42) // Error: you cannot initialize static member data like this
- {
- // ...
- }
-
- // You must define static data members this way:
- int Fred::j_ = 42;
-
- ==============================================================================
-
- [10.10] Why are classes with static data members getting linker errors?
-
- Because static data members must be explicitly defined in exactly one
- compilation unit[10.9]. If you didn't do this, you'll probably get an
- "undefined external" linker error. For example:
-
- // Fred.h
-
- class Fred {
- public:
- // ...
- private:
- static int j_; // Declares static data member Fred::j_
- // ...
- };
-
- The linker will holler at you ("Fred::j_ is not defined") unless you define (as
- opposed to merely declare) Fred::j_ in (exactly) one of your source files:
-
- // Fred.cpp
-
- #include "Fred.h"
-
- int Fred::j_ = some_expression_evaluating_to_an_int;
-
- // Alternatively, if you wish to use the implicit 0 value for static ints:
- // int Fred::j_;
-
- The usual place to define static data members of class Fred is file Fred.cpp
- (or Fred.C or whatever source file extension you use).
-
- ==============================================================================
-
- [10.11] What's the "static initialization order fiasco"?
-
- A subtle way to kill your project.
-
- The static initialization order fiasco is a very subtle and commonly
- misunderstood aspect of C++. Unfortunately it's very hard to detect -- the
- errors occur before main() begins.
-
- In short, suppose you have two static objects x and y which exist in separate
- source files, say x.cpp and y.cpp. Suppose further that the constructor for
- the y object calls some method on the x object.
-
- That's it. It's that simple.
-
- The tragedy is that you have a 50%-50% chance of dying. If the compilation
- unit for x.cpp happens to get initialized first, all is well. But if the
- compilation unit for y.cpp get initialized first, then y's constructor will get
- run before x's constructor, and you're toast. I.e., y's constructor will call
- a method on the x object, yet the x object hasn't yet been constructed.
-
- I hear they're hiring down at McDonalds. Enjoy your new job flipping burgers.
-
- If you think it's "exciting" to play Russian Roulette with live rounds in half
- the chambers, you can stop reading here. On the other hand if you like to
- improve your chances of survival by preventing disasters in a systematic way,
- you probably want to read the next FAQ[10.12].
-
- Note: The static initialization order fiasco does not apply to
- builtin/intrinsic types like int or char*. For example if you create a static
- float object, there is never a problem with static initialization order. The
- only time the static initialization order is truly a fiasco is when your static
- or global objects have a constructor.
-
- ==============================================================================
-
- [10.12] How do I prevent the "static initialization order fiasco"?
-
- Use the "construct on first use" idiom, which simply means to wrap your static
- object inside a function.
-
- For example, suppose you have two classes, Fred and Barney. There is a global
- Fred object called x, and a global Barney object called y. Barney's
- constructor invokes the goBowling() method on the x object. The file x.cpp
- defines the x object:
-
- // File x.cpp
- #include "Fred.hpp"
- Fred x;
-
- The file y.cpp defines the y object:
-
- // File y.cpp
- #include "Barney.hpp"
- Barney y;
-
- For completeness the Barney constructor might look something like this:
-
- // File Barney.cpp
- #include "Barney.hpp"
-
- Barney::Barney()
- {
- // ...
- x.goBowling();
- // ...
- }
-
- As described above[10.11], the disaster occurs if y is constructed before x,
- which happens 50% of the time since they're in different source files.
-
- There are many solutions to this problem, but a very simple and completely
- portable solution is to replace the global Fred object, x, with a global
- function, x(), that returns the Fred object by reference.
-
- // File x.cpp
-
- #include "Fred.hpp"
-
- Fred& x()
- {
- static Fred* ans = new Fred();
- return *ans;
- }
-
- Since static local objects are constructed the first time control flows over
- their declaration (only), the above new Fred() statement will only happen once:
- the first time x() is called. Every subsequent call will return the same Fred
- object (the one pointed to by ans). Then all you do is change your usages of x
- to x():
-
- // File Barney.cpp
- #include "Barney.hpp"
-
- Barney::Barney()
- {
- // ...
- x().goBowling();
- // ...
- }
-
- This is called the Construct On First Use Idiom because it does just that: the
- global Fred object is constructed on its first use.
-
- The downside of this approach is that the Fred object is never destructed. The
- C++ FAQ Book has a second technique that answers this concern (but at the cost
- of opening a "static de-initialization order fiasco").
-
- Note: You don't have to do this for builtin/intrinsic types like int or char*.
- For example if you create a static or global float object, there is no need to
- wrap it within a function. The only time the static initialization order is
- truly a fiasco is when your static or global objects have a constructor.
-
- ==============================================================================
-
- [10.13] How do I prevent the "static initialization order fiasco" for my static
- data members?
-
- Just use the same technique just described[10.12], but this time use a static
- member function rather than a global function.
-
- Suppose you have a class X that has a static Fred object:
-
- // File X.hpp
-
- class X {
- public:
- // ...
-
- private:
- static Fred x_;
- };
-
- Naturally this static member is initialized separately:
-
- // File X.cpp
-
- #include "X.hpp"
-
- Fred X::x_;
-
- Naturally also the Fred object will be used in one or more of X's methods:
-
- void X::someMethod()
- {
- x_.goBowling();
- }
-
- But now the "disaster scenario" is if someone somewhere somehow calls this
- method before the Fred object gets constructed. For example, if someone else
- creates a static X object and invokes its someMethod() method during static
- initialization, then you're at the mercy of the compiler as to whether the
- compiler will construct X::x_ before or after the someMethod() is called.
- (Note that the ANSI/ISO C++ committee is working on this problem, but compilers
- aren't yet generally available that handle these changes; watch this space for
- an update in the future.)
-
- In any event, it's always portable and safe to change the X::x_ static data
- member into a static member function:
-
- // File X.hpp
-
- class X {
- public:
- // ...
-
- private:
- static Fred& x();
- };
-
- Naturally this static member is initialized separately:
-
- // File X.cpp
-
- #include "X.hpp"
-
- Fred& X::x()
- {
- static Fred* ans = new Fred();
- return *ans;
- }
-
- Then you simply change any usages of x_ to x():
-
- void X::someMethod()
- {
- x().goBowling();
- }
-
- If you're super performance sensitive and you're concerned about the overhead
- of an extra function call on each invocation of X::someMethod() you can set up
- a static Fred& instead. As you recall, static local are only initialized once
- (the first time control flows over their declaration), so this will call X::x()
- only once: the first time X::someMethod() is called:
-
- void X::someMethod()
- {
- static Fred& x = X::x();
- x.goBowling();
- }
-
- Note: You don't have to do this for builtin/intrinsic types like int or char*.
- For example if you create a static or global float object, there is no need to
- wrap it within a function. The only time the static initialization order is
- truly a fiasco is when your static or global objects have a constructor.
-
- ==============================================================================
-
- [10.14] How can I handle a constructor that fails?
-
- Throw an exception. See [17.1] for details.
-
- ==============================================================================
-
- SECTION [11]: Destructors
-
-
- [11.1] What's the deal with destructors?
-
- A destructor gives an object its last rites.
-
- Destructors are used to release any resources allocated by the object. E.g.,
- class Lock might lock a semaphore, and the destructor will release that
- semaphore. The most common example is when the constructor uses new, and the
- destructor uses delete.
-
- Destructors are a "prepare to die" member function. They are often abbreviated
- "dtor".
-
- ==============================================================================
-
- [11.2] What's the order that local objects are destructed?
-
- In reverse order of construction: First constructed, last destructed.
-
- In the following example, b's destructor will be executed first, then a's
- destructor:
-
- void userCode()
- {
- Fred a;
- Fred b;
- // ...
- }
-
- ==============================================================================
-
- [11.3] What's the order that objects in an array are destructed?
-
- In reverse order of construction: First constructed, last destructed.
-
- In the following example, the order for destructors will be a[9], a[8], ...,
- a[1], a[0]:
-
- void userCode()
- {
- Fred a[10];
- // ...
- }
-
- ==============================================================================
-
- [11.4] Can I overload the destructor for my class?
-
- No.
-
- You can have only one destructor for a class Fred. It's always called
- Fred::~Fred(). It never takes any parameters, and it never returns anything.
-
- You can't pass parameters to the destructor anyway, since you never explicitly
- call a destructor[11.5] (well, almost never[11.10]).
-
- ==============================================================================
-
- [11.5] Should I explicitly call a destructor on a local variable?
-
- No!
-
- The destructor will get called again at the close } of the block in which the
- local was created. This is a guarantee of the language; it happens
- automagically; there's no way to stop it from happening. But you can get
- really bad results from calling a destructor on the same object a second time!
- Bang! You're dead!
-
- ==============================================================================
-
- [11.6] What if I want a local to "die" before the close } of the scope in which
- it was created? Can I call a destructor on a local if I really want to?
-
- No! [For context, please read the previous FAQ[11.5]].
-
- Suppose the (desirable) side effect of destructing a local File object is to
- close the File. Now suppose you have an object f of a class File and you want
- File f to be closed before the end of the scope (i.e., the }) of the scope of
- object f:
-
- void someCode()
- {
- File f;
-
- // ... [This code that should execute when f is still open] ...
-
- // <-- We want the side-effect of f's destructor here!
-
- // ... [This code that should execute after f is closed] ...
- }
-
- There is a simple solution to this problem[11.7]. But in the mean time,
- remember: Do not explicitly call the destructor![11.5]
-
- ==============================================================================
-
- [11.7] OK, OK already; I won't explicitly call the destructor of a local; but
- how do I handle the above situation?
-
- [For context, please read the previous FAQ[11.6]].
-
- Simply wrap the extent of the lifetime of the local in an artificial block {
- ... }:
-
- void someCode()
- {
- {
- File f;
- // ... [This code will execute when f is still open] ...
- }
- // ^-- f's destructor will automagically be called here!
-
- // ... [This code will execute after f is closed] ...
- }
-
- ==============================================================================
-
- [11.8] What if I can't wrap the local in an artificial block? [UPDATED!]
-
- [Recently added the note about constructors at the end (on 3/00).]
-
- Most of the time, you can limit the lifetime of a local by wrapping the local
- in an artificial block ({ ... })[11.7]. But if for some reason you can't do
- that, add a member function that has a similar effect as the destructor. But
- do not call the destructor itself!
-
- For example, in the case of class File, you might add a close() method.
- Typically the destructor will simply call this close() method. Note that the
- close() method will need to mark the File object so a subsequent call won't
- re-close an already-closed File. E.g., it might set the fileHandle_ data
- member to some nonsensical value such as -1, and it might check at the
- beginning to see if the fileHandle_ is already equal to -1:
-
- class File {
- public:
- void close();
- ~File();
- // ...
- private:
- int fileHandle_; // fileHandle_ >= 0 if/only-if it's open
- };
-
- File::~File()
- {
- close();
- }
-
- void File::close()
- {
- if (fileHandle_ >= 0) {
- // ... [Perform some operating-system call to close the file] ...
- fileHandle_ = -1;
- }
- }
-
- Note that the other File methods may also need to check if the fileHandle_ is
- -1 (i.e., check if the File is closed).
-
- Note also that any constructors that don't actually open a file should set
- fileHandle_ to -1.
-
- ==============================================================================
-
- [11.9] But can I explicitly call a destructor if I've allocated my object with
- new?
-
- Probably not.
-
- Unless you used placement new[11.10], you should simply delete the object
- rather than explicitly calling the destructor. For example, suppose you
- allocated the object via a typical new expression:
-
- Fred* p = new Fred();
-
- Then the destructor Fred::~Fred() will automagically get called when you delete
- it via:
-
- delete p; // Automagically calls p->~Fred()
-
- You should not explicitly call the destructor, since doing so won't release the
- memory that was allocated for the Fred object itself. Remember: delete p does
- two things[16.8]: it calls the destructor and it deallocates the memory.
-
- ==============================================================================
-
- [11.10] What is "placement new" and why would I use it?
-
- There are many uses of placement new. The simplest use is to place an object
- at a particular location in memory. This is done by supplying the place as a
- pointer parameter to the new part of a new expression:
-
- #include <new.h> // Must #include this to use "placement new"
- #include "Fred.h" // Declaration of class Fred
-
- void someCode()
- {
- char memory[sizeof(Fred)]; // Line #1
- void* place = memory; // Line #2
-
- Fred* f = new(place) Fred(); // Line #3 (see "DANGER" below)
- // The pointers f and place will be equal
-
- // ...
- }
-
- Line #1 creates an array of sizeof(Fred) bytes of memory, which is big enough
- to hold a Fred object. Line #2 creates a pointer place that points to the
- first byte of this memory (experienced C programmers will note that this step
- was unnecessary; it's there only to make the code more obvious). Line #3
- essentially just calls the constructor Fred::Fred(). The this pointer in the
- Fred constructor will be equal to place. The returned pointer f will therefore
- be equal to place.
-
- ADVICE: Don't use this "placement new" syntax unless you have to. Use it only
- when you really care that an object is placed at a particular location in
- memory. For example, when your hardware has a memory-mapped I/O timer device,
- and you want to place a Clock object at that memory location.
-
- DANGER: You are taking sole responsibility that the pointer you pass to the
- "placement new" operator points to a region of memory that is big enough and is
- properly aligned for the object type that you're creating. Neither the
- compiler nor the run-time system make any attempt to check whether you did this
- right. If your Fred class needs to be aligned on a 4 byte boundary but you
- supplied a location that isn't properly aligned, you can have a serious
- disaster on your hands (if you don't know what "alignment" means, please don't
- use the placement new syntax). You have been warned.
-
- You are also solely responsible for destructing the placed object. This is
- done by explicitly calling the destructor:
-
- void someCode()
- {
- char memory[sizeof(Fred)];
- void* p = memory;
- Fred* f = new(p) Fred();
- // ...
- f->~Fred(); // Explicitly call the destructor for the placed object
- }
-
- This is about the only time you ever explicitly call a destructor.
-
- ==============================================================================
-
- [11.11] When I write a destructor, do I need to explicitly call the destructors
- for my member objects?
-
- No. You never need to explicitly call a destructor (except with placement
- new[11.10]).
-
- A class's destructor (whether or not you explicitly define one) automagically
- invokes the destructors for member objects. They are destroyed in the reverse
- order they appear within the declaration for the class.
-
- class Member {
- public:
- ~Member();
- // ...
- };
-
- class Fred {
- public:
- ~Fred();
- // ...
- private:
- Member x_;
- Member y_;
- Member z_;
- };
-
- Fred::~Fred()
- {
- // Compiler automagically calls z_.~Member()
- // Compiler automagically calls y_.~Member()
- // Compiler automagically calls x_.~Member()
- }
-
- ==============================================================================
-
- [11.12] When I write a derived class's destructor, do I need to explicitly call
- the destructor for my base class?
-
- No. You never need to explicitly call a destructor (except with placement
- new[11.10]).
-
- A derived class's destructor (whether or not you explicitly define one)
- automagically invokes the destructors for base class subobjects. Base classes
- are destructed after member objects. In the event of multiple inheritance,
- direct base classes are destructed in the reverse order of their appearance in
- the inheritance list.
-
- class Member {
- public:
- ~Member();
- // ...
- };
-
- class Base {
- public:
- virtual ~Base(); // A virtual destructor[20.4]
- // ...
- };
-
- class Derived : public Base {
- public:
- ~Derived();
- // ...
- private:
- Member x_;
- };
-
- Derived::~Derived()
- {
- // Compiler automagically calls x_.~Member()
- // Compiler automagically calls Base::~Base()
- }
-
- Note: Order dependencies with virtual inheritance are trickier. If you are
- relying on order dependencies in a virtual inheritance hierarchy, you'll need a
- lot more information than is in this FAQ.
-
- ==============================================================================
-
- SECTION [12]: Assignment operators
-
-
- [12.1] What is "self assignment"? [UPDATED!]
-
- [Recently changed "knowning" to "knowing" thanks to Stan Brown (on 1/00).]
-
- Self assignment is when someone assigns an object to itself. For example,
-
- #include "Fred.hpp" // Declares class Fred
-
- void userCode(Fred& x)
- {
- x = x; // Self-assignment
- }
-
- Obviously no one ever explicitly does a self assignment like the above, but
- since more than one pointer or reference can point to the same object
- (aliasing), it is possible to have self assignment without knowing it:
-
- #include "Fred.hpp" // Declares class Fred
-
- void userCode(Fred& x, Fred& y)
- {
- x = y; // Could be self-assignment if &x == &y
- }
-
- int main()
- {
- Fred z;
- userCode(z, z);
- }
-
- ==============================================================================
-
- [12.2] Why should I worry about "self assignment"?
-
- If you don't worry about self assignment[12.1], you'll expose your users to
- some very subtle bugs that have very subtle and often disastrous symptoms. For
- example, the following class will cause a complete disaster in the case of
- self-assignment:
-
- class Wilma { };
-
- class Fred {
- public:
- Fred() : p_(new Wilma()) { }
- Fred(const Fred& f) : p_(new Wilma(*f.p_)) { }
- ~Fred() { delete p_; }
- Fred& operator= (const Fred& f)
- {
- // Bad code: Doesn't handle self-assignment!
- delete p_; // Line #1
- p_ = new Wilma(*f.p_); // Line #2
- return *this;
- }
- private:
- Wilma* p_;
- };
-
- If someone assigns a Fred object to itself, line #1 deletes both this->p_ and
- f.p_ since *this and f are the same object. But line #2 uses *f.p_, which is
- no longer a valid object. This will likely cause a major disaster.
-
- The bottom line is that you the author of class Fred are responsible to make
- sure self-assignment on a Fred object is innocuous[12.3]. Do not assume that
- users won't ever do that to your objects. It is your fault if your object
- crashes when it gets a self-assignment.
-
- Aside: the above Fred::operator= (const Fred&) has a second problem: If an
- exception is thrown[17] while evaluating new Wilma(*f.p_) (e.g., an
- out-of-memory exception[16.5] or an exception in Wilma's copy
- constructor[17.1]), this->p_ will be a dangling pointer -- it will
- point to memory that is no longer valid. This can be solved by allocating
- the new objects before deleting the old objects.
-
- ==============================================================================
-
- [12.3] OK, OK, already; I'll handle self-assignment. How do I do it?
- [UPDATED!]
-
- [Recently reworded the last paragraph thanks to Stan Brown (on 1/00).]
-
- You should worry about self assignment every time you create a class[12.2].
- This does not mean that you need to add extra code to all your classes: as long
- as your objects gracefully handle self assignment, it doesn't matter whether
- you had to add extra code or not.
-
- If you do need to add extra code to your assignment operator, here's a simple
- and effective technique:
-
- Fred& Fred::operator= (const Fred& f)
- {
- if (this == &f) return *this; // Gracefully handle self assignment[12.1]
-
- // Put the normal assignment duties here...
-
- return *this;
- }
-
- This explicit test isn't always necessary. For example, if you were to fix the
- assignment operator in the previous FAQ[12.2] to handle exceptions thrown by
- new[16.5] and/or exceptions thrown by the copy constructor[17.1] of class
- Wilma, you might produce the following code. Note that this code has the
- (pleasant) side effect of automatically handling self assignment as well:
-
- Fred& Fred::operator= (const Fred& f)
- {
- // This code gracefully (albeit implicitly) handles self assignment[12.1]
- Wilma* tmp = new Wilma(*f.p_); // It would be OK if an exception[17] got thrown here
- delete p_;
- p_ = tmp;
- return *this;
- }
-
- In cases like the previous example (where self assignment is harmless but
- inefficient), some programmers want to add "if (this == &f) return *this;" to
- improve the efficiency of self assignment. This is generally the wrong
- tradeoff. If self assignment only occurs once in a thousand times, the if
- would waste cycles 99.9% of the time.
-
- ==============================================================================
-
- SECTION [13]: Operator overloading
-
-
- [13.1] What's the deal with operator overloading?
-
- It allows you to provide an intuitive interface to users of your class.
-
- Operator overloading allows C/C++ operators to have user-defined meanings on
- user-defined types (classes). Overloaded operators are syntactic sugar for
- function calls:
-
- class Fred {
- public:
- // ...
- };
-
- #if 0
-
- // Without operator overloading:
- Fred add(Fred, Fred);
- Fred mul(Fred, Fred);
-
- Fred f(Fred a, Fred b, Fred c)
- {
- return add(add(mul(a,b), mul(b,c)), mul(c,a)); // Yuk...
- }
-
- #else
-
- // With operator overloading:
- Fred operator+ (Fred, Fred);
- Fred operator* (Fred, Fred);
-
- Fred f(Fred a, Fred b, Fred c)
- {
- return a*b + b*c + c*a;
- }
-
- #endif
-
- ==============================================================================
-
- [13.2] What are the benefits of operator overloading?
-
- By overloading standard operators on a class, you can exploit the intuition of
- the users of that class. This lets users program in the language of the
- problem domain rather than in the language of the machine.
-
- The ultimate goal is to reduce both the learning curve and the defect rate.
-
- ==============================================================================
-
- [13.3] What are some examples of operator overloading?
-
- Here are a few of the many examples of operator overloading:
- * myString + yourString might concatenate two string objects
- * myDate++ might increment a Date object
- * a * b might multiply two Number objects
- * a[i] might access an element of an Array object
- * x = *p might dereference a "smart pointer" that actually "points" to a disk
- record -- it could actually seek to the location on disk where p "points"
- and return the appropriate record into x
-
- ==============================================================================
-
- [13.4] But operator overloading makes my class look ugly; isn't it supposed to
- make my code clearer?
-
- Operator overloading makes life easier for the users of a class[13.2], not for
- the developer of the class!
-
- Consider the following example.
-
- class Array {
- public:
- int& operator[] (unsigned i); // Some people don't like this syntax
- // ...
- };
-
- inline
- int& Array::operator[] (unsigned i) // Some people don't like this syntax
- {
- // ...
- }
-
- Some people don't like the keyword operator or the somewhat funny syntax that
- goes with it in the body of the class itself. But the operator overloading
- syntax isn't supposed to make life easier for the developer of a class. It's
- supposed to make life easier for the users of the class:
-
- int main()
- {
- Array a;
- a[3] = 4; // User code should be obvious and easy to understand...
- }
-
- Remember: in a reuse-oriented world, there will usually be many people who use
- your class, but there is only one person who builds it (yourself); therefore
- you should do things that favor the many rather than the few.
-
- ==============================================================================
-
- [13.5] What operators can/cannot be overloaded? [UPDATED!]
-
- [Recently got rid of #if/#else/#endif by splitting the example thanks to Stan
- Brown (on 1/00).]
-
- Most can be overloaded. The only C operators that can't be are . and ?: (and
- sizeof, which is technically an operator). C++ adds a few of its own
- operators, most of which can be overloaded except :: and .*.
-
- Here's an example of the subscript operator (it returns a reference). First
- without operator overloading:
-
- class Array {
- public:
- int& elem(unsigned i) { if (i > 99) error(); return data[i]; }
- private:
- int data[100];
- };
-
- int main()
- {
- Array a;
- a.elem(10) = 42;
- a.elem(12) += a.elem(13);
- }
-
- Now the same logic is presented with operator overloading:
-
- class Array {
- public:
- int& operator[] (unsigned i) { if (i > 99) error(); return data[i]; }
- private:
- int data[100];
- };
-
- int main()
- {
- Array a;
- a[10] = 42;
- a[12] += a[13];
- }
-
- ==============================================================================
-
- [13.6] Can I overload operator== so it lets me compare two char[] using a
- string comparison?
-
- No: at least one operand of any overloaded operator must be of some
- user-defined type (most of the time that means a class).
-
- But even if C++ allowed you to do this, which it doesn't, you wouldn't want to
- do it anyway since you really should be using a string-like class rather than
- an array of char in the first place[17.3] since arrays are evil[21.5].
-
- ==============================================================================
-
- [13.7] Can I create a operator** for "to-the-power-of" operations?
-
- Nope.
-
- The names of, precedence of, associativity of, and arity of operators is fixed
- by the language. There is no operator** in C++, so you cannot create one for a
- class type.
-
- If you're in doubt, consider that x ** y is the same as x * (*y) (in other
- words, the compiler assumes y is a pointer). Besides, operator overloading is
- just syntactic sugar for function calls. Although this particular syntactic
- sugar can be very sweet, it doesn't add anything fundamental. I suggest you
- overload pow(base,exponent) (a double precision version is in <math.h>).
-
- By the way, operator^ can work for to-the-power-of, except it has the wrong
- precedence and associativity.
-
- ==============================================================================
-
- [13.8] How do I create a subscript operator for a Matrix class?
-
- Use operator() rather than operator[].
-
- When you have multiple subscripts, the cleanest way to do it is with operator()
- rather than with operator[]. The reason is that operator[] always takes
- exactly one parameter, but operator() can take any number of parameters (in the
- case of a rectangular matrix, two paramters are needed).
-
- For example:
-
- class Matrix {
- public:
- Matrix(unsigned rows, unsigned cols);
- double& operator() (unsigned row, unsigned col);
- double operator() (unsigned row, unsigned col) const;
- // ...
- ~Matrix(); // Destructor
- Matrix(const Matrix& m); // Copy constructor
- Matrix& operator= (const Matrix& m); // Assignment operator
- // ...
- private:
- unsigned rows_, cols_;
- double* data_;
- };
-
- inline
- Matrix::Matrix(unsigned rows, unsigned cols)
- : rows_ (rows),
- cols_ (cols),
- data_ (new double[rows * cols])
- {
- if (rows == 0 || cols == 0)
- throw BadIndex("Matrix constructor has 0 size");
- }
-
- inline
- Matrix::~Matrix()
- {
- delete[] data_;
- }
-
- inline
- double& Matrix::operator() (unsigned row, unsigned col)
- {
- if (row >= rows_ || col >= cols_)
- throw BadIndex("Matrix subscript out of bounds");
- return data_[cols_*row + col];
- }
-
- inline
- double Matrix::operator() (unsigned row, unsigned col) const
- {
- if (row >= rows_ || col >= cols_)
- throw BadIndex("const Matrix subscript out of bounds");
- return data_[cols_*row + col];
- }
-
- Then you can access an element of Matrix m using m(i,j) rather than m[i][j]:
-
- int main()
- {
- Matrix m(10,10);
- m(5,8) = 106.15;
- cout << m(5,8);
- // ...
- }
-
- ==============================================================================
-
- [13.9] Why shouldn't my Matrix class's interface look like an array-of-array?
-
- Here's what this FAQ is really all about: Some people build a Matrix class that
- has an operator[] that returns a reference to an Array object, and that Array
- object has an operator[] that returns an element of the Matrix (e.g., a
- reference to a double). Thus they access elements of the matrix using syntax
- like m[i][j] rather than syntax like m(i,j)[13.8].
-
- The array-of-array solution obviously works, but it is less flexible than the
- operator() approach[13.8]. Specifically, there are easy performance tuning
- tricks that can be done with the operator() approach that are more difficult in
- the [][] approach, and therefore the [][] approach is more likely to lead to
- bad performance, at least in some cases.
-
- For example, the easiest way to implement the [][] approach is to use a
- physical layout of the matrix as a dense matrix that is stored in row-major
- form (or is it column-major; I can't ever remember). In contrast, the
- operator() approach[13.8] totally hides the physical layout of the matrix, and
- that can lead to better performance in some cases.
-
- Put it this way: the operator() approach is never worse than, and sometimes
- better than, the [][] approach.
- * The operator() approach is never worse because it is easy to implement the
- dense, row-major physical layout using the operator() approach, so when that
- configuration happens to be the optimal layout from a performance
- standpoint, the operator() approach is just as easy as the [][] approach
- (perhaps the operator() approach is a tiny bit easier, but I won't quibble
- over minor nits).
- * The operator() approach is sometimes better because whenever the optimal
- layout for a given application happens to be something other than dense,
- row-major, the implementation is often significantly easier using the
- operator() approach compared to the [][] approach.
-
- As an example of when a physical layout makes a significant difference, a
- recent project happened to access the matrix elements in columns (that is, the
- algorithm accesses all the elements in one column, then the elements in
- another, etc.), and if the physical layout is row-major, the accesses can
- "stride the cache". For example, if the rows happen to be almost as big as the
- processor's cache size, the machine can end up with a "cache miss" for almost
- every element access. In this particular project, we got a 20% improvement in
- performance by changing the mapping from the logical layout (row,column) to the
- physical layout (column,row).
-
- Of course there are many examples of this sort of thing from numerical methods,
- and sparse matrices are a whole other dimension on this issue. Since it is, in
- general, easier to implement a sparse matrix or swap row/column ordering using
- the operator() approach, the operator() approach loses nothing and may gain
- something -- it has no down-side and a potential up-side.
-
- Use the operator() approach[13.8].
-
- ==============================================================================
-
- [13.10] Should I design my classes from the outside (interfaces first) or from
- the inside (data first)? [UPDATED!]
-
- [Recently added friend relationship between LinkedListIterator and LinkedList
- thanks to M≤nica Garcφa Garcφa; reworded "In the case of OO software..."
- sentence thanks to Fabrice Clerc (on 3/00).]
-
- From the outside!
-
- A good interface provides a simplified view that is expressed in the vocabulary
- of a user[7.3]. In the case of OO software, the interface is normally the set
- of public methods of either a single class or a tight group of classes[14.2].
-
- First think about what the object logically represents, not how you intend to
- physically build it. For example, suppose you have a Stack class that will be
- built by containing a LinkedList:
-
- class Stack {
- public:
- // ...
- private:
- LinkedList list_;
- };
-
- Should the Stack have a get() method that returns the LinkedList? Or a set()
- method that takes a LinkedList? Or a constructor that takes a LinkedList?
- Obviously the answer is No, since you should design your interfaces from the
- outside-in. I.e., users of Stack objects don't care about LinkedLists; they
- care about pushing and popping.
-
- Now for another example that is a bit more subtle. Suppose class LinkedList is
- built using a linked list of Node objects, where each Node object has a pointer
- to the next Node:
-
- class Node { /*...*/ };
-
- class LinkedList {
- public:
- // ...
- private:
- Node* first_;
- };
-
- Should the LinkedList class have a get() method that will let users access the
- first Node? Should the Node object have a get() method that will let users
- follow that Node to the next Node in the chain? In other words, what should a
- LinkedList look like from the outside? Is a LinkedList really a chain of Node
- objects? Or is that just an implementation detail? And if it is just an
- implementation detail, how will the LinkedList let users access each of the
- elements in the LinkedList one at a time?
-
- One man's answer: A LinkedList is not a chain of Nodes. That may be how it is
- built, but that is not what it is. What it is is a sequence of elements.
- Therefore the LinkedList abstraction should provide a "LinkedListIterator"
- class as well, and that "LinkedListIterator" might have an operator++ to go to
- the next element, and it might have a get()/set() pair to access its value
- stored in the Node (the value in the Node element is solely the responsibility
- of the LinkedList user, which is why there is a get()/set() pair that allows
- the user to freely manipulate that value).
-
- Starting from the user's perspective, we might want our LinkedList class to
- support operations that look similar to accessing an array using pointer
- arithmetic:
-
- void userCode(LinkedList& a)
- {
- for (LinkedListIterator p = a.begin(); p != a.end(); ++p)
- cout << *p << '\n';
- }
-
- To implement this interface, LinkedList will need a begin() method and an end()
- method. These return a "LinkedListIterator" object. The "LinkedListIterator"
- will need a method to go forward, ++p; a method to access the current element,
- *p; and a comparison operator, p != a.end().
-
- The code follows. The key insight is that the LinkedList class does not have
- any methods that lets users access the Nodes. Nodes are an implementation
- technique that is completely buried. The LinkedList class could have its
- internals replaced with a doubly linked list, or even an array, and the only
- difference would be some performance differences with the prepend(elem) and
- append(elem) methods.
-
- #include <assert.h> // Poor man's exception handling
-
- typedef int bool; // Someday we won't have to do this
-
- class LinkedListIterator;
- class LinkedList;
-
- class Node {
- // No public members; this is a "private class"
- friend LinkedListIterator; // A friend class[14]
- friend LinkedList;
- Node* next_;
- int elem_;
- };
-
- class LinkedListIterator {
- public:
- bool operator== (LinkedListIterator i) const;
- bool operator!= (LinkedListIterator i) const;
- void operator++ (); // Go to the next element
- int& operator* (); // Access the current element
- private:
- LinkedListIterator(Node* p);
- Node* p_;
- friend LinkedList; // so LinkedList can construct a LinkedListIterator
- };
-
- class LinkedList {
- public:
- void append(int elem); // Adds elem after the end
- void prepend(int elem); // Adds elem before the beginning
- // ...
- LinkedListIterator begin();
- LinkedListIterator end();
- // ...
- private:
- Node* first_;
- };
-
- Here are the methods that are obviously inlinable (probably in the same header
- file):
-
- inline bool LinkedListIterator::operator== (LinkedListIterator i) const
- {
- return p_ == i.p_;
- }
-
- inline bool LinkedListIterator::operator!= (LinkedListIterator i) const
- {
- return p_ != i.p_;
- }
-
- inline void LinkedListIterator::operator++()
- {
- assert(p_ != NULL); // or if (p_==NULL) throw ...
- p_ = p_->next_;
- }
-
- inline int& LinkedListIterator::operator*()
- {
- assert(p_ != NULL); // or if (p_==NULL) throw ...
- return p_->elem_;
- }
-
- inline LinkedListIterator::LinkedListIterator(Node* p)
- : p_(p)
- { }
-
- inline LinkedListIterator LinkedList::begin()
- {
- return first_;
- }
-
- inline LinkedListIterator LinkedList::end()
- {
- return NULL;
- }
-
- Conclusion: The linked list had two different kinds of data. The values of the
- elements stored in the linked list are the responsibility of the user of the
- linked list (and only the user; the linked list itself makes no attempt to
- prohibit users from changing the third element to 5), and the linked list's
- infrastructure data (next pointers, etc.), whose values are the responsibility
- of the linked list (and only the linked list; e.g., the linked list does not
- let users change (or even look at!) the various next pointers).
-
- Thus the only get()/set() methods were to get and set the elements of the
- linked list, but not the infrastructure of the linked list. Since the linked
- list hides the infrastructure pointers/etc., it is able to make very strong
- promises regarding that infrastructure (e.g., if it was a doubly linked list,
- it might guarantee that every forward pointer was matched by a backwards
- pointer from the next Node).
-
- So, we see here an example of where the values of some of a class's data is the
- responsibility of users (in which case the class needs to have get()/set()
- methods for that data) but the data that the class wants to control does not
- necessarily have get()/set() methods.
-
- Note: the purpose of this example is not to show you how to write a linked-list
- class. In fact you should not "roll your own" linked-list class since you
- should use one of the "container classes" provided with your compiler. Ideally
- you'll use one of the standard container classes[32.1] such as the list<T>
- template.
-
- ==============================================================================
-
- --
- Marshall Cline / 972-931-9470 / mailto:cline@parashift.com
-