prefer the canonical form of assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_ctor, dtor, and...

22
“Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU, 2009 61 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, 2009 62 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 primitive types and STL types, just follow it.

Upload: others

Post on 17-Mar-2020

3 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

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

Page 2: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

“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?

Page 3: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

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

}

Page 4: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

“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

Page 5: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

“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

Page 6: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

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

Page 7: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

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

Page 8: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

“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???

Page 9: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

“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) );

}

};

Page 10: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

“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

Page 11: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

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

Page 12: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

“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

Page 13: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

“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

Page 14: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

“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

Page 15: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

“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()

Page 16: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

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

Page 17: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

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

Page 18: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

“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();

// ...

}

// ...

};

Page 19: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

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

Page 20: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

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

Page 21: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

“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().

Page 22: Prefer the Canonical Form of Assignment.web.ntnu.edu.tw/~tcchiang/tpp2009/3_Ctor, dtor, and assignment IV.pdf · “Ctor, Dtor, and Assignment,” The Practice of Programming, CSIE@NTNU,

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