条款 45: 弄清 C++在幕后为你所写、所调用的函数

         如果你没有声明下列函数,体贴的编译器会声明它自己的版本。这些函数是:一个拷贝构造函数,一个赋值运算符,一个析构函数,一对取址运算符。另外,如果你没有声明任何构造函数,它也将为你声明一个缺省构造函数。所有这些函数都是公有的。

class Empty {
public:
Empty(); //  缺省构造函数
Empty(const Empty& rhs); //  拷贝构造函数
~Empty(); //  析构函数----  是否
//   为虚函数看下文说明
Empty&
operator=(const Empty& rhs); //  赋值运算符
Empty* operator&(); //  取址运算符
const Empty* operator&() const;
};


const Empty e1; //  缺省构造函数
//   析构函数
Empty e2(e1); //  拷贝构造函数
e2 = e1; //  赋值运算符
Empty *pe2 = &e2; //  取址运算符
// ( 非 const)
const Empty *pe1 = &e1; //  取址运算符
// (const)

缺省构造函数和析构函数实际上什么也不做,它们只是让你能够创建和销毁类的对象。

这些函数实际上就如同下面所定义的那样:

inline Empty::Empty() {}
inline Empty::~Empty() {}
inline Empty * Empty::operator&() { return this; }
inline const Empty * Empty::operator&() const
{ return this; }

        至于 拷贝构造函数和赋值运算符:官方的规则是:缺省拷贝构造函数(赋值运算符)对 类的非静态数据成员进行" 以成员为单位的"  逐一拷贝构造(赋值)。即,如果 m 是类 C 中类型为 T 的非静态数据成员并且 C 没有声明拷贝构造函数(赋值运算符)m 将会通过类型 T 的拷贝构造函数(赋值运算符)被拷贝构造(赋值)----  如果 T 有拷贝构造函数(赋值运算符)的话。如果没有, 规则递归应用到 m 的数据成员,直至找到一个拷贝构造函数 (赋值运算符)固定类型(例如,int,double,指针,等)为止。默认情况下, 固定类型的对象拷贝构造(赋值)时是从源对象到目标对象的" 逐位"  拷贝。对于从别的类继承而来的类来说,这条规则适用于继承层次结构中的每一层,所以,用户自定义的构造函数和赋值运算符无论在哪一层被声明,都会被调用。

template<class T>
class NamedObject {
public:
	NamedObject(const char *name, const T& value){ nameValue=name;objectValue=value;}

private:
string nameValue;
T objectValue;
};

int main(int argc, char* argv[])
{
 NamedObject<int> no1("Smallest Prime Number", 2);
 NamedObject<int> no2(no1); //  调用拷贝构造函数
}

因为 NamedObject 类声明了至少一个构造函数编译器将不会生成缺省构造函数。

NamedObject<int> no1("Smallest Prime Number", 2);
NamedObject<int> no2(no1); //  调用拷贝构造函数


       编译器生成的拷贝构造函数必须分别用 no1.nameValue 和 no1.objectValue来初始化 no2.nameValue 和 no2.objectValue

nameValue 的类型是 string , string有一个拷贝构造函数。所以 no2.nameValue 初始化时将调用 string 的拷贝构造函数参数为no1.nameValue。另一方面,NamedObject<int>::objectValue 的类型是 int (因为这个模板实例中, T 是 int ), int 没有定义拷贝构造函数,所以 no2.objectValue是通过从 no1.objectValue 拷贝每一个比特(bit)而被初始化的


请注意:编译器生成的默认赋值运算符。生成默认赋值运算符的条件比较苛刻。

1.如果包含一个“内含引用成员”,不会生成默认的赋值操作符,就必须定义自己的默认拷贝赋值操作符

2.如果包含一个“const 成员”,不会生成默认的赋值操作符,就必须定义自己的默认拷贝赋值操作符

3.如果派生类的基类将标准赋值运算符声明为 private,  编译器也将拒绝为这个派生类生成赋值运算符


template<class T>
class NamedObject {
public:
	NamedObject(string& name, const T& value): nameValue(name),objectValue(value){ }
  //  同上,假设没有
//   声明 operator=
private:
<strong>string& nameValue; //  现在是一个引用
const T objectValue; //  现在为 const</strong>
};
int main(int argc, char* argv[])
{
string newDog("Persephone");
string oldDog("Satch");
NamedObject<int> p(newDog, 2);  
NamedObject<int> s(oldDog, 29);  
<strong>p=s;</strong>
}

编译就会出错: error C2582: “operator =”函数在“NamedObject<T>”中不可用

因为类包含了引用成员和const成员,所以,不会生成默认的赋值函数,

p=s就会出错;

理由:

        赋值之前,p.nameValue 指向某个 string 对象,s.nameValue 也指向一个string但并非同一个。赋值会给 p.nameValue 带来怎样的影响呢?赋值之后,p.nameValue 应该指向"被 s.nameValue 所指向的 string"  吗,即,引用本身应该被修改吗?如果是这样,那太阳从西边出来了,因为 C++没有办法让一个引用指向另一个不同的对象(参见条款 M1) 。或者, p.nameValue 所指的 string对象应该被修改吗?  这样的话,含有"指向那个 string 的指针或引用"  的其它对象也会受影响,也就是说,和赋值没有直接关系的其它对象也会受影响。

pointer和reference之间的一个重要差异是,pointer可以被重新赋值,指向另一个对象,reference却总是指向它最初获得的那个对象。


string s1("Nancy");

string s2("Clancy");

string& rs=s1;//rs代表s1

string* ps=&s1;//ps指向s1

rs=s2;//但是s1的值现在变成了“Clancy

ps=&s2;//ps指向了s2


       如果派生类的基类将标准赋值运算符声明为 private 编译器也将拒绝为这个派生类生成赋值运算符。因为,编译器为派生类生成的赋值运算符也应该处理基类部分(见条款 16 和 M33) ,但这样做的话,就得调用对派生类来说无权访问的基类成员函数,这当然是不可能的.





  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值