>

Monday, 14 December 2015

FAQ on constructors

1.Is there any difference between List x; and List x();?
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)
  ...
}


2. After p = new Fred[n], how does the compiler know there are n objects to be destructed during delete[] p?
The run-time system stores the number of objects, n, somewhere where it can be retrieved if you only know the pointer, p. There are two popular techniques that do this. Both these techniques are in use by commercial-grade compilers, both have tradeoffs, and neither is perfect. These techniques are:

If the compiler uses the associative array technique, the code for p = new Fred[n] looks something like this (where arrayLengthAssociation is the imaginary name of a hidden, global associative array that maps from void* to "size_t"):
// Original code: Fred* p = new Fred[n];
Fred* p = (Fred*) operator new[] (n * sizeof(Fred));
size_t i;
try {
  for (i = 0; i < n; ++i)
    new(p + i) Fred();           // Placement new
}
catch (...) {
  while (i-- != 0)
    (p + i)->~Fred();            // Explicit call to the destructor
  operator delete[] (p);
  throw;
}
arrayLengthAssociation.insert(p, n);
Then the delete[] p statement becomes:
// Original code: delete[] p;
size_t n = arrayLengthAssociation.lookup(p);
while (n-- != 0)
  (p + n)->~Fred();
operator delete[] (p);


3. Can one constructor of a class call another constructor of the same class to initialize the this object?
Nope.
Let's work an example. Suppose you want your constructor Foo::Foo(char) to call another constructor of the same class, say Foo::Foo(char,int), in order that Foo::Foo(char,int) would help initialize the this object. Unfortunately there's no way to do this in C++.
Some people do it anyway. Unfortunately it doesn't do what they want. For example, the line Foo(x, 0); does not call Foo::Foo(char,int) on the this object. Instead it calls Foo::Foo(char,int) to initialize a temporary, local object (not this), then it immediately destructs that temporary when control flows over the ;.
class Foo {
public:
  Foo(char x);
  Foo(char x, int y);
  ...
};

Foo::Foo(char x)
{
  ...
  Foo(x, 0);  // this line does NOT help initialize the this object!!
  ...
}
You can sometimes combine two constructors via a default parameter:
class Foo {
public:
  Foo(char x, int y=0);  // this line combines the two constructors
  ...
};




4. 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];
  ...
}

5. 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;
  ...
}

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! 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;

  ...code that should execute when f is still open...

   We want the side-effect of f's destructor here!

  ...code that should execute after f is closed...
}
Simply wrap the extent of the lifetime of the local in an artificial block {...}:
void someCode()
{
  {
    File f;
    ...code that should execute when f is still open...
  } f's destructor will automatically be called here!

  ...code that should execute after f is closed...
}

7. Should my destructor throw an exception when it detects a problem?
Write a message to a log-file. But do not throw an exception!
The C++ rule is that you must never throw an exception from a destructor that is being called during the "stack unwinding" process of another exception. For example, if someone says throw Foo(), the stack will be unwound so all the stack frames between the throw Foo() and the } catch (Foo e) { will get popped. This is called stack unwinding. During stack unwinding, all the local objects in all those stack frames are destructed. If one of those destructors throws an exception (say it throws a Bar object), the C++ runtime system is in a no-win situation: should it ignore the Bar and end up in the } catch (Foo e) { where it was originally headed? Should it ignore the Foo and look for a } catch (Bar e) { handler? There is no good answer — either choice loses information.
So the C++ language guarantees that it will call terminate() at this point, and terminate() kills the process. The easy way to prevent this is never throw an exception from a destructor. But if you really want to be clever, you can say never throw an exception from a destructor while processing another exception. But in this second case, you're in a difficult situation: the destructor itself needs code to handle both throwing an exception and doing "something else", and the caller has no guarantees as to what might happen when the destructor detects an error . So the whole solution is harder to write. So the easy thing to do is always do "something else". That is, never throw an exception from a destructor.
8. 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>        // 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 newoperator 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.

9.What is the purpose of the explicit keyword?
To tell the compiler that a certain constructor may not be used to implicitly cast an expression to its class type.
The explicit keyword is an optional decoration for constructors that take exactly one argument. It only applies to single-argument constructors since those are the only constructors that can be used in type casting.
For example, without the explicit keyword the following code is valid:
class Foo {
public:
  Foo(int x);
};

class Bar {
public:
  Bar(double x);
};

void yourCode()
{
  Foo a = 42;         //OK: calls Foo::Foo(int) passing 42 as an argument
  Foo b(42);          //OK: calls Foo::Foo(int) passing 42 as an argument
  Foo c = Foo(42);    //OK: calls Foo::Foo(int) passing 42 as an argument
  Foo d = (Foo)42;    //OK: calls Foo::Foo(int) passing 42 as an argument

  Bar x = 3.14;       //OK: calls Bar::Bar(double) passing 3.14 as an argument
  Bar y(3.14);        //OK: calls Bar::Bar(double) passing 3.14 as an argument
  Bar z = Bar(3.14);  //OK: calls Bar::Bar(double) passing 3.14 as an argument
  Bar w = (Bar)3.14;  //OK: calls Bar::Bar(double) passing 3.14 as an argument
}
But sometimes you want to prevent this sort of implicit promotion or implicit type conversion. For example, if Foo is really an array-like container and 42 is the initial size, you might want to let your users say, Foo x(42); or perhaps Foo x = Foo(42);, but not just Foo x = 42;. If that's the case, you should use the explicit keyword:

class Foo {
public:
  explicit Foo(int x);
};

class Bar {
public:
  explicit Bar(double x);
};

void yourCode()
{
  Foo a = 42;         //Compile-time error: can't convert 42 to an object of type Foo
  Foo b(42);          //OK: calls Foo::Foo(int) passing 42 as an argument
  Foo c = Foo(42);    //OK: calls Foo::Foo(int) passing 42 as an argument
  Foo d = (Foo)42;    //OK: calls Foo::Foo(int) passing 42 as an argument

  Bar x = 3.14;       //Compile-time error: can't convert 3.14 to an object of type Bar
  Bar y(3.14);        //OK: calls Bar::Bar(double) passing 3.14 as an argument
  Bar z = Bar(3.14);  //OK: calls Bar::Bar(double) passing 3.14 as an argument
  Bar w = (Bar)3.14;  //OK: calls Bar::Bar(double) passing 3.14 as an argument
}

10. Is the default constructor for Fred always Fred::Fred()?
No. A "default constructor" is a constructor that can be called with no arguments. One example of this is a constructor that takes no parameters:
class Fred {
public:
  Fred();   // Default constructor: can be called with no args
  ...
};
Another example of a "default constructor" is one that 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
  ...
};

0 comments:

Post a comment