“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200961
Prefer the Canonical Form of
Assignment.
� Let your operator= be a member function with one
of the following two signatures:
T& operator=(const T&); // classic
T& operator=(T); // if you want a copy of the argument
� Return a reference to *this. Don’t return const T&.
� Copy all parts of an object.
� Handle assignment to self.
� Avoid making your assignment operator virtual.
� If you really need it, prefer to provide a named function instead (e.g. virtual void Assign(const T&); )
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200962
Prefer the Canonical Form of
Assignment.
� Return a reference to *this. Don’t return const T&.
class CPlayer {
public:
CPlayer & operator = (const CPlayer &) { // ...
return *this;
}
};
void func(int &) { // ...
}
void func(CPlayer &) { // ...
}
int main()
{
int n1, n2, n3;
n1 = n2 = n3;
func(n1 = n2);
CPlayer p1, p2, p3;
p1 = p2 = p3;
func(p1 = p2);
return 0;
}
Reason:
Since this behavior is followed by all primitivetypes and STL types, just follow it.
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200963
Prefer the Canonical Form of
Assignment.
� Copy all parts of an object.
class CPlayer {
public:
// no copy assignment is defined.private:
std::string name_;
int money_;
// ...
};
int main()
{
CPlayer p1, p2;
p1 = p2;
}
class CPlayer {
public:
CPlayer & operator= (const CPlayer &rhs){
name_ = rhs.name_;
money_ = rhs.money_;
return *this;
}
private:
std::string name_;
int money_;
// ...
};
int main()
{
CPlayer p1, p2;
p1 = p2;
}
Compiler-generated copying functionswork well.
When you define your own copying
functions (and ctors), remember toupdate them when you add new
data members.
// add a new memberint someValue_;
// add a new memberint someValue_;
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200964
Prefer the Canonical Form of
Assignment.
� Copy all parts of an object.
class CPowerPlayer : public CPlayer {
public:
CPowerPlayer()
:power_(0) {
}
CPowerPlayer(const CPowerPlayer &rhs)
:power_(rhs.power_) {
}
CPowerPlayer & operator = (const CPowerPlayer &rhs) {
power_ = rhs.power_;
}
private:
int power_;
};
int main()
{
CPowerPlayer pp1;
pp1.SetName(“Jeter”);
CPowerPlayer pp2(pp1), pp3;
pp3 = pp1;
}
class CPlayer {
private:
std::string name_;
int money_;
};
What’s wrong with them?
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200965
Prefer the Canonical Form of
Assignment.
� Copy all parts of an object.
class CPowerPlayer : public CPlayer {
public:
// ...
CPowerPlayer(const CPowerPlayer &rhs)
:CPlayer(rhs),
power_(rhs.power_) {
}
CPowerPlayer & operator = (const CPowerPlayer &rhs) {
CPlayer::operator=(rhs);
power_ = rhs.power_;
}
private:
int power_;
};
int main()
{
CPowerPlayer pp1;
pp1.SetName(“Jeter”);
CPowerPlayer pp2(pp1), pp3;
pp3 = pp1;
}
When you write a copying function, ensure that
(1) copy all local data members(2) call suitable copying functions for all base
classes.
(In this example, we don’t need to write the two
copying functions by ourselves. The compiler’s version is fine.)
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200966
Prefer the Canonical Form of
Assignment.
� Handle assignment to self.
� Am I stupid to do self-assignment?
int main()
{
CPowerPlayer pp1, pp2;
CPowerPlayer pparr[5];
CPowerPlayer *ptrpp1, *ptrpp2;
CPlayer &refp = pp1;
int i, j;
// ...
pp1 = pp1; // I am not that stupid.
pparr[i] = pparr[j]; // Am I sure that i ≠ j?
*ptrpp1 = *ptrpp2; // Am I sure that ptrpp1 ≠ ptrpp2?
refp = *ptrpp1; // Am I sure that refp ≠ *ptrpp1?
return 0;
}
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200967
Prefer the Canonical Form of
Assignment.
� Handle assignment to self.
� Wrong implementation – version 1.0
class CImage { ... };
class CPlayer {
public:
CPlayer & operator = (const CPlayer &rhs);
private:
CImage *image_;
// ...
};
CPlayer & CPlayer::operator= (const CPlayer &rhs)
{
image_ = new CImage(*rhs.image_);
return *this;
} memory leak
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200968
Prefer the Canonical Form of
Assignment.
� Handle assignment to self.
� Wrong implementation – version 1.1
class CImage { ... };
class CPlayer {
public:
CPlayer & operator = (const CPlayer &rhs);
private:
CImage *image_;
// ...
};
CPlayer & CPlayer::operator= (const CPlayer &rhs)
{
delete image_;
image_ = new CImage(*rhs.image_);
return *this;
} no dealing with self-assignment
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200969
Prefer the Canonical Form of
Assignment.
� Handle assignment to self.
� Wrong implementation – version 2
class CImage { ... };
class CPlayer {
public:
CPlayer & operator = (const CPlayer &rhs);
private:
CImage *image_;
// ...
};
CPlayer & CPlayer::operator= (const CPlayer &rhs)
{
if (this == &rhs) return *this;
delete image_;
image_ = new CImage(*rhs.image_);
return *this;
}
not exception-safe (what if new CImage throws exception?)
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200970
Prefer the Canonical Form of
Assignment.
� Handle assignment to self.
� Correct implementation – version 1
class CImage { ... };
class CPlayer {
public:
CPlayer & operator = (const CPlayer &rhs);
private:
CImage *image_;
// ...
};
CPlayer & CPlayer::operator= (const CPlayer &rhs)
{
CImage *pOrig = image_;
image_ = new CImage(*rhs.image_);
delete pOrig;
return *this;
}self-assignment safe
exception safe
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200971
Prefer the Canonical Form of
Assignment.
� Handle assignment to self.
� Correct(?) implementation – version 1 (Is it still exception-safe?)
class CImage { ... };
class CPlayer {
public:
CPlayer & operator = (const CPlayer &rhs);
private:
CImage *image1_, *image2_;
// ...
};
CPlayer & CPlayer::operator= (const CPlayer &rhs)
{
CImage *pOrig = image1_;
image1_ = new CImage(*rhs.image1_);
delete pOrig;
pOrig = image2_;
image2_ = new CImage(*rhs.image2_);
delete pOrig;
return *this;
}not strong exception-safe (what if new CImage(*rhs.image2) throws exception?)
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200972
Prefer the Canonical Form of
Assignment.
� Handle assignment to self.
� The most recommended implementation (copy and swap tech.)
class CPlayer {
public:
CPlayer(const CPlayer &rhs) { ... }
CPlayer & operator = (const CPlayer &rhs);
void swap(CPlayer &rhs) { ... }
// ...
};
CPlayer & CPlayer::operator= (const CPlayer &rhs)
{
CPlayer temp(rhs);
swap(temp);
return *this;
}
In principle, the swap() function should be no-fail.
If self-assignment is frequent due to reference aliasing or other reasons, it’s okay to still check for self-
assignment anyway as an optimization check to avoid needless check.
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 2009
Exercise
� Add two more operator to your simulated String class.
class String
{
public:
// 1. default constructor
// 2. copy constructor
// 3. constructor with one parameter //
with type const char *
// 4. destructor
// 5. size()
// 6. copy assignment
// 7. operator []
private:
char *str_;
};
73
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200974
Prefer the Canonical Form of
Assignment.
� Avoid making your assignment operator virtual.
� What inspires you to do that
class Animal {
public:
Animal & operator= (const Animal &rhs) {...}
};
class Lizard: public Animal {
Lizard & operator= (const Lizard &rhs) {...}
};
class Chicken: public Animal {
Chicken & operator= (const Chicken &rhs) {...}
};
int main()
{
Lizard liz1, liz2;
Animal *p1 = &liz1, *p2 = &liz2;
// ...
*p1 = *p2;
return 0;
}
// Only the “Animal” part of “Lizard” is copied.
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200975
Prefer the Canonical Form of
Assignment.
� Avoid making your assignment operator virtual.
� Virtual assignment solves one problem, but generates another.
class Animal {
public:
virtual Animal & operator= (const Animal &rhs) {...}
};
class Lizard: public Animal {
virtual Lizard & operator= (const Animal &rhs) {...}
};
class Chicken: public Animal {
virtual Chicken & operator= (const Animal &rhs) {...}
};
int main()
{
Lizard liz1, liz2;
Animal *p1 = &liz1, *p2 = &liz2;
// ...
*p1 = *p2;
return 0;
}
// Now, the assignment is okay. BUT, …
The prototype is changed in order to override
the virtual function in the derived class.
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200976
Prefer the Canonical Form of
Assignment.
� Avoid making your assignment operator virtual.
� Virtual assignment solves one problem, but generates another.
class Animal {
public:
virtual Animal & operator= (const Animal &rhs) {...}
};
class Lizard: public Animal {
virtual Lizard & operator= (const Animal &rhs) {...}
};
class Chicken: public Animal {
virtual Chicken & operator= (const Animal &rhs) {...}
};
int main()
{
Lizard liz;
Chicken chick;
Animal *p1 = &liz, *p2 = &chick;
// ...
*p1 = *p2;
return 0;
}
// Assign a chicken to a lizard???
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200977
Prefer the Canonical Form of
Assignment.
� Avoid making your assignment operator virtual.
� The problem can be solved in a sophisticated way.
class Lizard: public Animal {
public:
Lizard & operator= (const Lizard& rhs) { ... }
virtual Lizard & operator= (const Animal &rhs) {
return operator=( dynamic_cast<const Lizard&>(rhs) );
}
};
int main()
{
Lizard liz1, liz2;
Chicken chick;
Animal *p1 = &liz1, *p2 = &liz2, *p3 = &chick;
liz1 = liz2; // call Lizard & operator= (const Lizard& rhs)
*p1 = *p2; // call virtual operator=(), successful
*p1 = *p3; // call virtual operator=(), throw bad_cast exceptionreturn 0;
}
Provide this function to save dynamic_cast
cost for normal assignment.
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200978
Prefer the Canonical Form of
Assignment.
� Avoid making your assignment operator virtual.
� Although this implementation works, it asks the clients of Lizard to catch bad_cast exceptions and do corresponding
actions. Few programmers like this.
class Lizard: public Animal {
public:
Lizard & operator= (const Lizard& rhs) { ... }
virtual Lizard & operator= (const Animal &rhs) {
return operator=( dynamic_cast<const Lizard&>(rhs) );
}
};
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200979
Exercise
� Write a base class CPlayer and
three classes
� CAmazon,
� CPaladin, and
� CSorceress
derived from CPlayer to make the left
program show the following result:
I am an amazon!
I am a paladin!
I am a sorceress!
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200980
Exercise
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200981
Exercise
I am an amazon!
I am a paladin!
I am a sorceress!
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200982
Exercise
We have 0 amazons, 0 paladins, and 0 sorceress.
We have 1 amazons, 1 paladins, and 1 sorceress.
We have 2 amazons, 3 paladins, and 1 sorceress.We have 1 amazons, 1 paladins, and 1 sorceress.
We have 0 amazons, 1 paladins, and 1 sorceress.
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200983
Declare Destructors Virtual in
Polymorphic Base Classes.int main() {
CPlayer *p[3] = {new CAmazon,
new CPaladin,
new CSorceress
};
for (int i=0; i<3; ++i) {
p[i]->Display();
}
// ...
for (int i=0; i<3; ++i) {
delete p[i];
}
}
class CPlayer
{
public:
CPlayer() { ... }
~CPlayer() { ... }
virtual void Display() = 0;
// ...
};
class CAmazon : public CPlayer {
public:
virtual void Display() { }
// ...
};
class CPaladin : public CPlayer {
public:
virtual void Display() { }
// ...
};
class CSorceress : public CPlayer {
virtual void Display() { }
// ...
};
It is an undefined behavior to delete a derived class object through a base
class pointer with non-virtual destructor.
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200984
Declare Destructors Virtual in
Polymorphic Base Classes.
� Declare the destructor virtual when your class
contains at least one virtual member function.class CPlayer {
public:
CPlayer() { ... }
virtual ~CPlayer() { ... }
virtual void Display() = 0;
// ...
};
// ...
int main() {
CPlayer *p[3] = {new CAmazon,
new CPaladin,
new CSorceress
};
// ...
for (int i=0; i<3; ++i) {
delete p[i];
}
}Now its behavior is correct
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200985
Declare Destructors Virtual in
Polymorphic Base Classes.� For those classes that are not designed as base classes. Write
the document clearly to tell the clients not to inherit from it.
class SpecialString: public std::string
{
// ...
};
int main()
{
SpecialString *pss = new SpecialString(“Ah...”);
std::string *ps;
// ...
ps = pss;
// ...
delete ps;
}
std::string is not designed to be a base
class. You may use composition rather than inheritance.
• A simple “derivation prohibition strategy” is to make all constructors private and provide a public
static method to instantiate the object (like a factory function).
• Another strategy resorts to friend and virtual inheritance.
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200986
Declare Destructors Virtual in
Polymorphic Base Classes.� For those base classes that are not designed for polymorphic
use (they do not have virtual member functions),
make their destructors
� protected to avoid misuse of polymorphism
� non-virtual to avoid unnecessary increase of object size
int main() {
cout << sizeof(CPoint) << ' ' << sizeof(CLargePoint);
return 0;
}
class CLargePoint {
public:
virtual ~CLargePoint() {}
private:
int x, y;
};
class CPoint {
protected:
~CPoint() {}
private:
int x, y;
};
The size of CLargePoint objects gets larger since the invocation of virtual member functions
is usually achieved by a virtual table pointer (vptr) to a virtual table of virtual member functions.
8 12
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200987
Never Call Virtual Functions during
Construction and Destruction.
class Transaction {
public:
Transaction();
virtual void logTransaction() const = 0;
// ...
};
Transaction::Transaction()
{
// ...
logTransaction();
}
class BuyTransaction: public Transaction {
public:
virtual void logTransaction() const;
};
class SellTransaction: public Transaction {
public:
virtual void logTransaction() const;
};
What’s the problem here?
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200988
Never Call Virtual Functions during
Construction and Destruction.
� Construction of a derived class object
� Step1. Calling the constructors of base class(es) following
the order in which they appear in the class derivation list.
� Step2. Calling the constructors for non-static member class
objects following the order in which they are declared.
� Step3. Entering the computation phase of the constructor of
the derived class.
class Derived: public Base1, public Base2 {
public:
Derived() {
// computation phase
}
private:
Component1 c1;
Component2 c2;
};
Steps of Construction
Base1()
Base2()
Component1()
Component2()
// computation phase
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200989
Never Call Virtual Functions during
Construction and Destruction.� During execution of constructors of the base class, virtual
functions of the derived class will not be invoked.
� Rationale:
Calling virtual functions of the derived class before the derived
part is initialized is not reasonable.
� Standard:
During construction of base part, the type of object is the base
class rather than the derived class.
class Transaction {
public:
Transaction();
virtual void logTransaction() const = 0;
// ...
};
Transaction::Transaction() {
// ...
logTransaction();
}
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200990
Never Call Virtual Functions during
Construction and Destruction.
� Destruction of a derived class object
� The execution follows the reverse order of construction.
class Derived: public Base1, public Base2 {
public:
Derived() {
// computation phase
}
private:
Component1 c1;
Component2 c2;
};
Steps of Destruction
// computation phase
~Component2()
~Component1()~Base2()
~Base1()
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200991
Never Call Virtual Functions during
Construction and Destruction.� During execution of destructors of the base class, virtual
functions of the derived class will not be invoked.
� Rationale:
Calling virtual functions of the derived class after the derived
part is destroyed is not reasonable.
� Standard:
During destruction of base part, the type of object is the base
class rather than the derived class.
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200992
Never Call Virtual Functions during
Construction and Destruction.
� Can the compiler/linker help us?
� In some simple cases, they might be able to detect it.
class Transaction {
public:
Transaction();
virtual void logTransaction()
const = 0;
// ...
};
Transaction::Transaction()
{
// ...
logTransaction();
}
class BuyTransaction
: public Transaction
{
public:
virtual void logTransaction() const{}
};
class SellTransaction
: public Transaction
{
public:
virtual void logTransaction() const{}
};
int main()
{
BuyTransaction bTrans;
return 0;
}DevC++4980:
In constructor `Transaction::Transaction()': abstract virtual `virtual void
Transaction::logTransaction() const' called from constructor
// compilation warning or linkage error!
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200993
Never Call Virtual Functions during
Construction and Destruction.
� But in more complex cases, compiler/linker can not
detect the problem.class Transaction {
public:
Transaction();
virtual void logTransaction()
const = 0;
void legal() { init(); }
private:
void init(){ logTransaction(); }
};
Transaction::Transaction() {
init();
}
class BuyTransaction
: public Transaction
{
public:
virtual void logTransaction() const{}
};
class SellTransaction
: public Transaction
{
public:
virtual void logTransaction() const{}
};
int main()
{
BuyTransaction bTrans;
bTrans.legal();
return 0;
}
// no warning or error!The program will terminate with the following message:
pure virtual method called
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200994
Never Call Virtual Functions during
Construction and Destruction.
� In the worst case, the program just goes “smoothly”
with potential errors.class Transaction {
public:
Transaction();
virtual void logTransaction()
const {
// ...
}
private:
};
Transaction::Transaction() {
logTransaction();
}
class BuyTransaction
: public Transaction
{
public:
virtual void logTransaction() const{}
};
class SellTransaction
: public Transaction
{
public:
virtual void logTransaction() const{}
};
int main()
{
BuyTransaction bTrans;
return 0;
}
// no warning or error!
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200995
Never Call Virtual Functions during
Construction and Destruction.
� Solutions to post-construction (immediately calling a virtual function after constructing the
entire object)
1. Pass the duty: Just document that user code must do it.
2. Post-initialize lazily: Do it during the first call of a member function. A boolean flag in the base class tells whether or
not post-construction has taken place yet.
3. Pass information from derived class to base class.
4. Use a factory function: This way, you can easily force a
mandatory invocation of a post-constructor function.
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200996
Never Call Virtual Functions during
Construction and Destruction.
� Solutions to post-construction: Post-initialize lazily
class Transaction {
public:
Transaction():isPostInit_(false) {}
virtual void logTransaction() const { ... }
protected:
bool isPostInit_;
void PostInit() {
if (!isPostInit_) { logTransaction(); isPostInit_ = true; }
}
};
class BuyTransaction: public Transaction {
public:
virtual void logTransaction() const {}
void AnyMemberFunc() {
PostInit();
// ...
}
// ...
};
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200997
Never Call Virtual Functions during
Construction and Destruction.
� Solutions to post-construction: Pass information
class Transaction {
public:
explicit Transaction(const std::string & logInfo);
void logTransaction(const std::string &logInfo) const; // non-virtualprivate:
};
Transaction::Transaction() {const std::string &logInfo) {
// ...
logTransaction(logInfo);
}
class BuyTransaction: public Transaction {
public:
BuyTransaction( parameters )
: Transaction( createLogString(parameters) ) { ... }
private:
static std::string createLogString( parameters );
};
FAQ1: Why explicit?
FAQ2: Why static?
FAQ1: To prohibit implicit conversion from std::string to Transaction.
FAQ2: To avoid using uninitialized data members in the BuyTransaction object.
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200998
Never Call Virtual Functions during
Construction and Destruction.
� Solutions to post-construction: Use a factory function
class Transaction {
public:
Transaction() {}
virtual void logTransaction()
const {} // still virtual
template<class T>
static T * Create() {
T *p = new T;
p->logTransaction();
return p;
}
// ...
};
class BuyTransaction
: public Transaction
{
public:
virtual void logTransaction() const{}
};
class SellTransaction
: public Transaction
{
public:
virtual void logTransaction() const{}
};
int main() {
BuyTransaction *p = BuyTransaction::Create<BuyTransaction>();
return 0;
}
Constraints: (1) The derived class must not expose any constructor. (2) Construction can be achieved only through new operation.
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 200999
Copy and Destroy Consistently.
� What you create, also clean up:If you define any of the copy constructor (CC), copy assignment
operator (CA), or destructor (D), you might need to define one or
both of the others.
� If you write/disable either CC or CA, you probably need to do the
same for the other since they should have similar effects.
� If you explicitly write CC and CA (usu. to allocate or duplicate some
resource), you need to deallocate it in D.
� If you explicitly write D to manually release a resource, you
probably need to do duplication carefully or to disable copying.
� Prefer compiler-generated special members; only these can be
classified as “trivial,” and at least one major STL vendor heavily
optimizes for classes having trivial special members.
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 2009100
Avoid Slicing.
� Is there something wrong?
class Base { ... };
class Derived : public Base { ... };
void StrangeFunc(Base obj) { ... }
void PolyFunc(Base &obj) {
StrangeFunc(obj);
// ...
}
int main()
{
Derived d;
PolyFunc(d);
}
The programmer intends to manipulate Base and Base-derived objects polymorphically.
However, when StrangeFunc() is invoked, the object passed in is only the sliced Basepart of the Derived object.
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 2009101
Avoid Slicing.
� In base classes, consider disabling the copying
functions.
class Base {
// ...
protected:
Base(const Base&) { ... }
};
class Derived : public Base {
// ...
protected:
Derived(const Derived &rhs):Base(rhs) { ... }
};
void StrangeFunc(Base obj) { ... }
int main() {
Derived d;
StrangeFunc(d);
}
// Compilation error
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 2009102
Avoid Slicing.
� Instead, provide a virtual clone function for
polymorphic copying.
class Base {
public:
virtual Base* Clone() const = 0;
// ...
protected:
Base(const Base&) { ... }
};
class Derived : public Base {
public:
virtual Derived* Clone() const { return new Derived(*this); }
// ...
protected:
Derived(const Derived &rhs):Base(rhs) { ... }
};
There is still one problem, a further-derived class in the hierarchy can still forget to implement Clone().
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 2009103
Avoid Slicing.
� We can apply the NonVirtual Interface (NVI) pattern
and put a valuable assertion.class Base {
public:
Base* Clone() const {
Base* p = DoClone();
assert( typeid(*p) == typeid(*this)
&& “DoClone incorrect!” );
return p;
}
// ...
protected:
Base(const Base&) { ... }
private:
virtual Base* DoClone() const = 0;
};
class Derived : public Base
{
private:
virtual Derived* DoClone() const
{
return new Derived(*this);
}
};
class DerDer : public Derived
{
// Forget to provide DoClone()
};
int main() {
DerDer dd;
Base *p1 = &dd, *p2;
p2 = p1->Clone();
}// Causes run-time due to violation of assertion.
Top Related