Part 3
Q: When I write a destructor, do I need to explicitly call the destructors for my member objects?
A: No. You never need to explicitly call a destructor (except with placement new).
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()
}
Q: When I write a derived class's destructor, do I need to explicitly call the destructor for my base class?
A: No. You never need to explicitly call a destructor (except with placement new).
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
...
};
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.
Q: Is there any difference between List x; and List x();?
A: 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)
...
}
Q: Can one constructor of a class call another constructor of the same class to initialize the this object?
A: 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
...
};
If that doesn't work, e.g., if there isn't an appropriate default parameter that combines the two constructors, sometimes you can share their common code in a private init() member function:
class Foo {
public:
Foo(char x);
Foo(char x, int y);
...
private:
void init(char x, int y);
};
Foo::Foo(char x)
{
init(x, int(x) + 7);
...
}
Foo::Foo(char x, int y)
{
init(x, y);
...
}
void Foo::init(char x, int y)
{
...
}
BTW do NOT try to achieve this via placement new. Some people think they can say new(this) Foo(x, int(x)+7) within the body of Foo::Foo(char). However that is bad, bad, bad. Please don't write me and tell me that it seems to work on your particular version of your particular compiler; it's bad. Constructors do a bunch of little magical things behind the scenes, but that bad technique steps on those partially constructed bits. Just say no.
Q: Is the default constructor for Fred always Fred::Fred()?
A: 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
...
};
Q: Which constructor gets called when I create an array of Fred objects?
A: Fred's default constructor (except as discussed below.
class Fred {
public:
Fred();
...
};
int main()
{
Fred a[10]; calls the default constructor 10 times
Fred* p = new Fred[10]; calls the default constructor 10 times
...
}
If your class doesn't have a default constructor, you'll get a compile-time error when you attempt to create an array using the above simple syntax:
class Fred {
public:
Fred(int i, int j); assume there is no default constructor
...
};
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, even if your class already has a default constructor, you should try to use std::vector rather than an array (arrays are evil). std::vector lets you decide to use any constructor, not just the default constructor:
#include
int main()
{
std::vector a(10, Fred(5,7)); the 10 Fred objects in std::vector a will be initialized with Fred(5,7)
...
}
Even though you ought to use a std::vector rather than an array, there are times when an array might be the right thing to do, and for those, you might need the "explicit initialization of arrays" syntax. Here's how:
class Fred {
public:
Fred(int i, int j); assume there is no default constructor
...
};
int main()
{
Fred a[10] = {
Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7), // The 10 Fred objects are
Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7) // initialized using 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. Finally, you can use placement-new to manually initialize the elements of the array. Warning: it's ugly: the raw array can't be of type Fred, so you'll need a bunch of pointer-casts to do things like compute array index operations. Warning: it's compiler- and hardware-dependent: you'll need to make sure the storage is aligned with an alignment that is at least as strict as is required for objects of class Fred. Warning: it's tedious to make it exception-safe: you'll need to manually destruct the elements, including in the case when an exception is thrown part-way through the loop that calls the constructors. But if you really want to do it anyway, read up on placementnew. (BTW placement-new is the magic that is used inside of std::vector. The complexity of getting everything right is yet another reason to use std::vector.)
By the way, did I ever mention that arrays are evil? Or did I mention that you ought to use a std::vector unless there is a compelling reason to use an array?
A: No. You never need to explicitly call a destructor (except with placement new).
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()
}
Q: When I write a derived class's destructor, do I need to explicitly call the destructor for my base class?
A: No. You never need to explicitly call a destructor (except with placement new).
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
...
};
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.
Q: Is there any difference between List x; and List x();?
A: 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)
...
}
Q: Can one constructor of a class call another constructor of the same class to initialize the this object?
A: 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
...
};
If that doesn't work, e.g., if there isn't an appropriate default parameter that combines the two constructors, sometimes you can share their common code in a private init() member function:
class Foo {
public:
Foo(char x);
Foo(char x, int y);
...
private:
void init(char x, int y);
};
Foo::Foo(char x)
{
init(x, int(x) + 7);
...
}
Foo::Foo(char x, int y)
{
init(x, y);
...
}
void Foo::init(char x, int y)
{
...
}
BTW do NOT try to achieve this via placement new. Some people think they can say new(this) Foo(x, int(x)+7) within the body of Foo::Foo(char). However that is bad, bad, bad. Please don't write me and tell me that it seems to work on your particular version of your particular compiler; it's bad. Constructors do a bunch of little magical things behind the scenes, but that bad technique steps on those partially constructed bits. Just say no.
Q: Is the default constructor for Fred always Fred::Fred()?
A: 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
...
};
Q: Which constructor gets called when I create an array of Fred objects?
A: Fred's default constructor (except as discussed below.
class Fred {
public:
Fred();
...
};
int main()
{
Fred a[10]; calls the default constructor 10 times
Fred* p = new Fred[10]; calls the default constructor 10 times
...
}
If your class doesn't have a default constructor, you'll get a compile-time error when you attempt to create an array using the above simple syntax:
class Fred {
public:
Fred(int i, int j); assume there is no default constructor
...
};
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, even if your class already has a default constructor, you should try to use std::vector rather than an array (arrays are evil). std::vector lets you decide to use any constructor, not just the default constructor:
#include
int main()
{
std::vector a(10, Fred(5,7)); the 10 Fred objects in std::vector a will be initialized with Fred(5,7)
...
}
Even though you ought to use a std::vector rather than an array, there are times when an array might be the right thing to do, and for those, you might need the "explicit initialization of arrays" syntax. Here's how:
class Fred {
public:
Fred(int i, int j); assume there is no default constructor
...
};
int main()
{
Fred a[10] = {
Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7), // The 10 Fred objects are
Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7), Fred(5,7) // initialized using 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. Finally, you can use placement-new to manually initialize the elements of the array. Warning: it's ugly: the raw array can't be of type Fred, so you'll need a bunch of pointer-casts to do things like compute array index operations. Warning: it's compiler- and hardware-dependent: you'll need to make sure the storage is aligned with an alignment that is at least as strict as is required for objects of class Fred. Warning: it's tedious to make it exception-safe: you'll need to manually destruct the elements, including in the case when an exception is thrown part-way through the loop that calls the constructors. But if you really want to do it anyway, read up on placementnew. (BTW placement-new is the magic that is used inside of std::vector. The complexity of getting everything right is yet another reason to use std::vector.)
By the way, did I ever mention that arrays are evil? Or did I mention that you ought to use a std::vector unless there is a compelling reason to use an array?
0 comments:
Post a Comment