EffectiveC++(2)----构造/析构/赋值运算-创新互联
若写下
创新互联服务紧随时代发展步伐,进行技术革新和技术进步,经过10余年的发展和积累,已经汇集了一批资深网站策划师、设计师、专业的网站实施团队以及高素质售后服务人员,并且完全形成了一套成熟的业务流程,能够完全依照客户要求对网站进行成都网站设计、成都网站制作、建设、维护、更新和改版,实现客户网站对外宣传展示的首要目的,并为客户企业品牌互联网化提供全面的解决方案。class Empty { };
当C++处理过它之后,编译器就会为它声明一个default构造函数,一个copy构造函数,一个copy assignment操作符和一个析构函数。这就好像写下这样的代码:
class Empty {
public:
Empty() { ... }
Empty(const Empty& rhs) { ... }
~Empty() { ... }
Empty& operator=(const Empty& rhs) { ... }
}
惟有这些函数被需要(被调用),它们才会被编译器创建出来。
Empty e1; //default构造函数
Empty e2(e1); //copy构造函数
e2 = e1; //copy assignment操作符
default构造函数和析构函数主要是给编译器一个地方来放置“藏身幕后”的代码,像是调用base classes和non-static成员变量的构造函数和析构函数。注意,编译器产出的析构函数是个non-virtual。copy构造函数和copy assignment操作符,编译器创建的版本只是单纯地将来源于对象的每一个non-static成员变量拷贝到目标对象。
templateclass NameObject {
public:
NameObject(const char* name, const T& value);
NameObject(const std::string name, const T& value);
private:
std::string nameValue;
T objectValue;
};
NameObjectno1("Smallest Prime Number", 2);
NameObjectno2(no1);
编译器生成的copy构造函数必须以no1.nameValue和no1.objectValue为初值设定no2.nameValue和no2.objectValue。nameValue的类型是string,调用string的copy构造函数。objectValue的类型是int,是个内置类型,所以会拷贝每一个bits来完成初始化。
若NameObject的定义如下,其中的nameValue是个referencetostring,objectValue是个const T:
templateclass NameObject {
public:
NameObject(std::string& name, const T& value);
private:
std::string &nameValue; //是个reference
const T objectVaule; //是个const
};
std::string newDog("Persephone");
std::string oldDog("Satch");
NameObjectp(newDog, 2);
NameObjects(oldDog, 36);
p = s; // p的成员变量该发生什么事?
编译器拒绝编译赋值动作。
如果某个base classes将copy assignment操作符声明成private,编译器将拒绝为derived classes生成一个copy assignment操作符。
请记住:
编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符,以及析构函数。
条款06:若不想使用编译器自动生成的函数,就该明确拒绝用一个class描述待售房屋
class HomeForSale { ... };
每个售卖的房屋都是独一无二的,所以HomeForSale对象不能被复制。
HomeForSale h1;
HomeForSale h2;
HomeForSale h3(h1); //企图拷贝h1,不应通过编译
h1 = h2; //企图拷贝h2,不应通过编译
如果不声明copy构造函数或copyassignment操作符,编译器可能会为你产出一份,支持copying;
如果声明了copy构造函数或copyassignment操作符,那就还是支持copying。
所有编译器产出的函数都是public,我们可以把copy构造函数和copyassignment操作符声明成private。但是成员函数和友元函数还是可以调用private函数,解决办法就是“将成员函数声明为private而且故意不实现它们”。
class HomeForSale {
private:
HomeForSale(const HomeForSale&);
HomeForSale& operator=(const HomeForSale &);
};
请记住:
为驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明为private并且不予实现。
条款07:为多态基类声明virtual析构函数class TimeKeeper {
public:
TimeKeeper();
~TimeKeeper();
};
class AtomicClock : public TimeKeeper { ... };
class WaterClock : public TimeKeeper { ... };
class WristWatch : public TimeKeeper { ... };
// 使用工厂模式函数,返回一个指向derived class对象的base class指针
TimerKeeper* ptk = getTimeKeeper();
...
delete ptk;
C++明确指出,当derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,其结果未有定义——实际执行时通常发生的是对象的derived成分没被销毁。解决问题办法很简单,给base class一个virtual的析构函数。
class TimeKeeper {
public:
TimeKeeper();
virtual ~TimeKeeper();
};
TimeKeeper *ptk = getTimeKeeper();
...
delete ptk;
任何class只要带有virtual函数都几乎确定应该有个virtual析构函数。
如果class不含有virtual函数,通常表示它并不意图被用作一个base class。当class不企图被当作base class,令其析构函数为virtual往往是个馊主意。
class Point {
public:
Point(int xCoord, int yCoord);
~Point();
private:
int x, y;
};
欲实现出virtual函数,对象必须携带某些信息,主要用来控制运行期决定哪一个virtual函数该被调用。这份信息通常是由一个所谓vptr(vitrual table pointer)指针指出。vptr指向一个由函数指针构成的数组,成为vtbl(vitrual table);每一个带有virtual函数的class都有一个对应的vtbl。当对象调用某一virtual函数,实际调用的函数取决于该对象的vptr所指的那个vtbl——编译器在其中寻找适当的函数指针。
若Point class内含virtual函数,其对象的体积会增加。
std::string,STL中容器如vector,list,set等等都不带有virtual析构函数,不要继承一个标准容器或其他“带有non-virtual析构函数”的class。
纯虚函数(pure virtual),拥有纯虚函数的类是抽象类。
class AWON {
public:
virtual ~AWON() = 0;
};
析构函数的运作方式是,最深层派生(most derived)的那个class其析构函数最先被调用,然后是其每一个base class的析构函数被调用。 编译器会在AWON的derived classes的析构函数中创建一个对~AWON的调用动作,所以必须为这个函数提供一份定义,否则链接器会发出抱怨。
请记住:
带有多态性质的base classes应该声明一个virtual的析构函数。如果class带有任何的virtual函数,那么它就应该拥有一个virtual析构函数。
classes的设计目的如果不是作为base classe使用,或不是为了具备多态性,就不应该声明virtual析构函数。
条款09:绝不在构造和析构过程中调用virtual函数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;
};
BuyTransaction b; //会发生什么?
BuyTransaction的构造函数被调用,但首先Transaction构造函数一定会更早被调用;Transaction构造函数的最后一行调用了virtual函数logTransaction,调用的是Transaction内的版本,而不是BuyTransaction内的版本。base class的构造期间virtual函数是不会下降到derived class阶层。同样道理也适用于析构函数。
class Transaction {
public:
explicit Transaction(const std::string& logInfo);
void logTransaction(const std::string& logInfo);
};
Transaction::Transaction(const std::string& logInfo) {
logTransaction(logInfo);
}
class BuyTransaction : public Transaction {
public:
BuyTransaction(parameters) : Transaction(createLogString(parameters)) {
}
private:
static std::string createLogString(parameters);
};
可以由derived classes将必要的信息向上传递给至base class的构造函数。BuyTransaction内的private static函数作用,此函数是static函数,就不可能意外指向“初期未成熟之BuyTransaction对象内尚未初始化的成员变量”。
请记住:
在构造和析构期间不要调用virtual函数,因为这类调用从不下降到derived class。
条款10:令operator=返回一个referenceto*this关于赋值,可以写成连锁形式
int x,y,z;
x = y = z = 15;
赋值采用的是右结合,所以上述赋值被解析为:
x = (y = (z = 15));
15先赋值给z,然后其结果(更新后的z)再赋值给y,然后其结果(更新后的y)再赋值给x。
为了实现连续赋值,赋值操作符必须返回一个reference指向操作符的左侧实参。
class Widget {
public:
Widget& operator=(const Widget& rhs) {
...
return *this;
}
Widget& operator+=(const Widget& rhs) {
...
return *this;
}
};
请记住:
令赋值操作符返回一个reference to*this。
条款11:在operator=中处理“自我赋值”class Widget { ... }
Widget w;
w = w;
看起来有点愚蠢,但它合法。
a[i] = a[j]; //当i和j有相同值时
*px = *py; //当px和py指向相同的东西
class Bitmap { ... };
class Widget {
private:
Bitmap* pb;
};
Widget& Widget::operator=(const Widget& rhs) {
delete pb; //删除原来的pb
pb = new Bitmap(*rhs.pb); //令pb指向*pb的一个副本
return *this;
}
增加“证同测试”
Widget& operator=(const Widget& rhs) {
if(this == &rhs) return *this;
...
}
当new Bitmap抛出异常时,pb保持原状。即使没有证同测试,这段代码还是能够处理自我赋值,虽然不是效率最高,但是它行的通。
Widget& Widget::operator=(const Widget& rhs) {
Bitmap* pOrg = pb; //记住原来的pb
pb = new Bitmap(*rhs.pb); //令pb指向*pb的一个副本
delete pOrg; //删除原来的pb
return *this;
}
一个常见而够好的operator=撰写方法如下:
class Widgt {
...
void swap(Widget& rhs); //交互*this和rhs的数据
...
};
Widget& Widget::operator=(const Widget& rhs) {
Widget temp(rhs);
swap(temp);
return *this;
}
另外一个变奏,是用以by value方式接受实参
Widget& Widget::operator=(Widget rhs) { //rhs是被传递对象的一个副本
swap(rhs); //将*this的数据和副本的数据交换
return *this;
}
请记住:
确保当对象自我赋值时operator=有良好的行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。
确认任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
条款12:赋值对象时勿忘其每一个成分void logCall(const std::string& funcName);
class Customer {
public:
Customer(const Customer& rhs);
Customer& operator=(const Customer& rhs);
private:
std::string name;
};
Customer::Customer(const Customer& rhs) : name(rhs.name) {
logCall("Customer copy constructor");
}
Customer& Customer::operator=(const Customer& rhs) {
logCall("Customer copy assignment operator");
name = rhs.name;
return *this;
}
当新增一个Date成员后:
class Date { ... };
class Customer {
public:
...
private:
std::string name;
Date lastTransaction;
};
这时既有的copying函数执行的是局部拷贝:它们的确复制了name,但是没有复制新增的lastTransaction。编译器不会提醒你。如果为class增加了一个成员变量,必须同时修改copying函数。
一旦发生继承,可能会造成一个潜藏危机。
class PriorityCustomer : public Customer {
public:
...
PriorityCustomer(const PriorityCustomer& rhs);
PriorityCustomer& operator=(const PriorityCustomer& rhs);
private:
int priority;
};
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs) : priority(rhs.priority) {
logCall("PriorityCustomer copy constructor");
}
PriorityCustomer& operator=::PriorityCustomer(const PriorityCustomer& rhs) {
logCall("PriorityCustomer copy constructor");
priority = rhs.priority;
return *this;
}
PriorityCustomer的copying函数看起来好像是复制了PriorityCustomer内的每样东西,再看一眼,它们复制了PriorityCustomer声明的成员变量,对继承自Customer的成员变量却未被复制。
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs) :
Customer(rhs), priority(rhs.priority) {
logCall("PriorityCustomer copy constructor");
}
PriorityCustomer& operator=::PriorityCustomer(const PriorityCustomer& rhs) {
logCall("PriorityCustomer copy constructor");
Customer::operator=(rhs);
priority = rhs.priority;
return *this;
}
请记住:
Copying函数应用有确保复制“对象内的所有成员变量”及“所有base class成分”;
不要尝试以某个copying函数调用另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数公用。
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧
网页题目:EffectiveC++(2)----构造/析构/赋值运算-创新互联
文章路径:http://scyanting.com/article/ippig.html