C++箴言:了解C++偷偷加上和调用了什么

C++箴言:了解C++偷偷加上和调用了什么,第1张

C++箴言:了解C++偷偷加上和调用了什么,第2张

几乎每个你自己编写的类都会有一个或多个构造函数,一个析构函数和一个复制赋值操作符。不要惊讶,那些功能就像你的面包和黄油一样。它们控制基本操作,例如创建一个新对象并确保它已被初始化,删除一个函数并确保它被完全清除,以及为该对象赋一个新值。这些函数的错误会引起你的类的深远而不愉快的反弹,所以保证它们的正确性是生死攸关的事情。在这一章中,我将提供一些关于如何组装这些函数来成为一个好类的主干的指导。

一类空什么时候会变成no 空?答案是C++拿到的时候。如果你自己没有声明一个复制构造函数,一个复制赋值操作符和一个析构函数,编译器会声明自己版本的这些东西。而且,如果你连自己都没有声明一个构造函数,编译器会为你声明一个默认的构造函数。所有这些函数都被声明为公共的和内联的(参见第30项)。因此,如果你写

类空{ };

本质上和你写的一样如下:

class Empty {
public:
Empty(){...} //默认构造函数
Empty(const Empty& rhs) {...} //复制构造函数
~Empty() {...} //析构函数-参见下面的
//以确定它是否为虚拟的
Empty & operator =(const Empty & RHS){...} //复制赋值运算符
};

这些函数只有在需要的时候才会产生,但是不用做太多就会用到。以下代码将导致生成每个函数:

空E1;//默认构造函数;
//析构函数

空E2(E1);//复制构造函数

e2 = e1//复制赋值运算符

假设编译器为你写了这些函数,那么它们是做什么的?默认的构造函数和析构函数主要是给编译器一个放置“幕后”代码的地方,比如调用基类和非静态数据成员的构造函数和析构函数。注意,生成的析构函数是非虚的,除非它从基类继承,基类声明了一个虚析构函数(在这种情况下,函数的虚拟性来自基类)。

编译器的复制构造函数和复制赋值操作符只是将每个非静态数据成员从原始对象复制到目标对象。例如,考虑一个NamedObject模板,它将一个名称与一个T类型的对象相关联:

template
class named object {
public:
named object(const char * name,const T & value);
named object(const STD::string & name,const T & value);
..
private:
STD::string name value;
T object value;
};
因为构造函数是在NamedObject中声明的,所以编译器将不再生成默认的构造函数。这一点非常重要。这意味着,如果你足够仔细地设计你的类,以至于它需要构造函数参数,你就不必担心编译器会不顾你的决定,贸然添加一个不需要参数的构造函数。

NamedObject既没有声明复制构造函数,也没有声明复制赋值操作符,所以编译器会生成这些函数(当然是在需要的时候)。看,这就是复制构造函数的用法:

NamedObject no1(“最小素数”,2);
named object NO2(no1);//调用复制构造函数

编译器生成的复制构造函数总是分别用no1.nameValue和no1.objectValue初始化no2.nameValue和no2.objectValue。值的类型是string,标准的string类型有一个复制构造函数,所以string的复制构造函数会被作为参数调用来初始化NO2。nameValue。另一方面,NamedObject::objectValue的类型是int(因为在这个模板的实例化中T是int),int是内置类型,所以no2.objectValue会通过复制no1.objectValue的每一位来初始化。

由编译器NamedObject生成的复制赋值操作符本质上将具有相同的行为。但是,通常情况下,编译器生成的复制赋值操作符,只有在结果代码合法且具有合理且可理解的逻辑时,才会有我描述的行为。如果这两个测试中的任何一个失败,编译器将拒绝为您的类生成运算符=。

例如,假设NamedObject定义如下,nameValue是对字符串的引用,objectValue是一个常量T:

模板

class named object {
public:
//此ctor不再接受常数值名称,因为nameValue
//现在是对非常数值字符串的引用。char*构造函数
//不见了,因为我们必须有一个字符串来引用。

NamedObject(std::string& name,const T & value);
...//如上,假设没有
// operator=声明为
private:
STD::string & name value;//这现在是一个引用
const T object value;//这现在是const
};

现在,考虑一下这里会发生什么:

STD::string newDog(" Persephone ");
STD::string old dog(" Satch ");

NamedObject p(newDog,2);//我最初写这个的时候,我们的
//狗珀尔塞福涅快要
//过两岁生日了
NamedObject s(老狗,36岁);//如果我家的狗Satch
//还活着的话,它应该已经36岁了

p = s;//p中的
//数据成员应该怎么处理?

在赋值之前,p.nameValue和s.nameValue都指向string对象,但它们并不相同。该作业对p.nameValue有什么影响?赋值后,p.nameValue领导的字符串和s.nameValue领导的字符串是一样的吗,即引用本身被改变了?如果是这样的话,这就违反了惯例,因为C++没有提供一种方法来引导对另一个对象的引用。换个思路,p.nameValue所引向的string对象是不是改变了,从而保持指针或者引用或者指向那个对象,也就是赋值不直接影响对象?这是编译器生成的复制赋值运算符应该做的吗?

面对这个问题,C++拒绝编译器生成代码。如果希望包含引用成员的类支持赋值,必须自己定义复制赋值操作符。当面对具有const成员的类时,编译器也会这样做(就像上面更改后的类中的objectValue一样)。更改const成员是非法的,所以编译器隐式生成的赋值函数无法决定如何处理它们。最后,如果基类将复制赋值操作符声明为私有,编译器将拒绝为其派生类生成隐式复制赋值操作符。毕竟编译器为派生类生成的复制赋值操作符也要处理它们的基类部分,但如果这样做了,当然也不能调用那些派生类无权调用的成员函数。

要记住的事情

编译器可以隐式生成类的默认构造函数、复制构造函数、复制赋值运算符和析构函数。

位律师回复
DABAN RP主题是一个优秀的主题,极致后台体验,无插件,集成会员系统
白度搜_经验知识百科全书 » C++箴言:了解C++偷偷加上和调用了什么

0条评论

发表评论

提供最优质的资源集合

立即查看 了解详情