Friday, August 6, 2010

Cpp Theory Questions 10

What if I can't wrap the local in an artificial block?

 

Most of the time, you can limit the lifetime of a local by wrapping the local in an artificial block ({...}). 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) {

...insert code to call the OS 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.

 

---------------------------------------------------------------------------------------------------------------------

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 // 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.

 

Note: there is a much cleaner but more sophisticated way of handling the destruction / deletion situation.

 

What is "self assignment"?

 

Self assignment is when someone assigns an object to itself. For example,

 

 

#include "Fred.h" // Defines 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.h" // Defines class Fred

 

void userCode(Fred& x, Fred& y)

{

x = y; // Could be self-assignment if &x == &y

}

 

int main()

{

Fred z;

userCode(z, z);

...

}

 

OK, OK, already; I'll handle self-assignment. How do I do it?

 

You should worry about self assignment every time you create a class. 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

 

// 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 to handle exceptions thrown by new and/or exceptions thrown by the copy constructor 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

Wilma* tmp = new Wilma(*f.p_); // It would be OK if an exception 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 improve the efficiency of self assignment by adding an otherwise unnecessary test, such as "if (this == &f) return *this;". It is generally the wrong tradeoff to make self assignment more efficient by making the non-self assignment case less efficient. For example, adding the above if test to the Fred assignment operator would make the non-self assignment case slightly less efficient (an extra (and unnecessary) conditional branch). If self assignment actually occured once in a thousand times, the if would waste cycles 99.9% of the time.

 

---------------------------------------------------------------------------------------------------------------------

No comments:

Post a Comment