您的位置:首页 > 编程语言 > C语言/C++

《c++primer》读书笔记二 复制控制

2012-05-09 20:08 253 查看
复制构造函数是一种特殊的构造函数,具有单个形参,该形参(常用const修饰)是对该类型的引用。

1.当定义一个新对象并用一个同类型的对象对它进行初始化时,将显示使用复制构造函数;

2.当将该类型的对象传递给函数或从函数返回该类型的对象时,将隐式使用复制构造函数。

当对象超出作用域或动态分配的对象被删除时,将自动应用析构函数。析构函数可用于释放对象构造时或在对象生命期中所获取的资源。不管类是否定义了自己的析构函数,编译器都自动执行类中非static数据成员的析构函数。

复制构造函数,赋值操作符和析构函数总称为复制控制。有一种特别常见的情况需要类定义自己的复制控制成员的:类具有指针成员。

13.1 复制构造函数

c++支持两种初始化形式:直接初始化和复制初始化。复制初始化使用"=",直接初始化将初始化式放在圆括号中。

string null_book="9-999-99999-9";//复制

string dot(10,'.');//直接

string empty_copy=string();//复制

string empty_direct;//直接

当形参为非引用类型的时候,将复制实参的值。类似地,以非引用类型做返回值时,将返回return语句中的值的副本。当形参或返回值为类类型时,由复制构造函数进行复制。

即使定义了其他构造函数,也会合成复制构造函数。合成复制构造函数的行为是,执行“逐个成员”初始化,将新对象初始化为原对象的副本。

“逐个成员”是指编译器将现有对象的每个非static成员,依次复制到正在创建的对象。合成复制构造函数直接复制内置类型成员的值,类类型成员使用该类的复制构造函数进行复制。数组成员的复制是一个例外。虽然一般不能复制数组,但如果一个类加油数组成员,则合成复制构造函数将复制数组。复制数组时合成复制构造函数将复制数组的每一个元素。

复制构造函数就是接受单个类类型引用形参(通常为const)的构造函数,如:

class Foo{

Foo();//构造函数

Foo(Const Foo &);//复制构造函数

};

有些类必须对复制对象时发生的事情加以控制。这样的类经常有一个数据成员是指针,或者有成员表示在构造函数中分配的其他资源。而另一些类在创建新对象时必须做一些特定工作。在这两种情况下,都必须定义复制构造函数。

复制构造函数的形参并不限制为const,但必须是一个引用。我自己的理解是:“如果不是引用,形参需调用拷贝构造函数来复制实参,这种调用是不合理的。”

Sales_item::Sales_item(Const Sales_item rhs);//error

为了防止复制,类必须显示声明其复制构造函数为private。

如果复制构造函数是private,将不允许用户代码复制该类类型的对象,编译器将拒绝任何进行复制的尝试。然而类的友元和成员仍可进行复制。如果想要连友元和成员中的复制也禁止,就可以声明一个(private)构造函数但不对其定义。

一般来说,最好显示或隐式定义默认构造函数和复制函数。只有不存在其他构造函数时才合成默认构造函数。如果定义了复制构造函数,也必须定义默认构造函数。

13.2赋值操作符

class Sales_item{

public:

Sales_item& operator=(const Sales_item&);//赋值操作符

};

合成赋值操作符与合成复制构造函数的操作类似。它逐个成员赋值:右操作数的每个成员赋值给左操作数对象的对应成员。除数组之外,每个成员用所属类型的常规方式赋值。对数组,每个数组元素赋值。

Sales_item& Sales_item::operator=(const Sales_item &rhs)

{

isbn=rhs.isbn;

units_sold=rhs.units_sold;

revenue=rhs.revenue;

return *this;

}

13.3析构函数

撤销类对象时会自动调用析构函数:

Sales_item *p=new Sales_item;//调用默认构造函数

{

Sales_item item(*p);//调用拷贝构造函数复制*p到item

delete p;//调用析构函数析构*p

}//变量item在超出作业域时应该自动撤销。因此,当遇到右花括号时,将运行item的析构函数。

动态分配的对象只有在指向该对象的指针被删除时才撤销。如果没有删除指向动态对象的指针,则不会与运行该对象的析构函数,对象就一直存在,从而导致内存泄露,而且,对象内部使用的如何资源也不会释放。

当对象的引用或指针超出作用域时,不会运行析构函数。只有删除指向动态分配对象的指针或实际对象(而不是引用)超出作用域时,才会运行析构函数。

撤销一个容器(标准库容器或内置数组)时,都会运行容器中类类型元素的析构函数。容器中的元素总是按逆序撤销:首先撤销下标为size()-1的元素,然后是size()-2的元素......直至最后撤销下标为0的元素。

#include<iostream>
using namespace std;

class A{
public:
A();
~A();
private:
static int data;
};

int A::data=0;

A::A(){
data=data+1;
cout<<"constructed "<<data<<endl;
}
A::~A(){
data=data-1;
cout<<"destructed "<<data<<endl;
}
int main(){
A *a=new A[10];
delete []a;//如果不显示的delete,并不会调用析构函数来释放动态分配的对象
return 0;
}

一般如果类需要析构函数,则它需要赋值操作符和复制构造函数,这是一个有用的经验法则。

合成析构函数按对象创建时的逆序撤销每个非static成员。

析构函数是个成员函数,它的名字是在类名字之前加上“~”,它没有返回值,没有形参,所以不能重载析构函数。析构函数与复制构造函数或赋值操作符之间的一个重要区别是,即使编写了自己的析构函数,合成析构函数仍然运行。

13.5 管理指针成员

包含指针的类需要特别注意复制控制,原因是复制指针时只需复制指针中的地址,而不会复制指针指向的对象。

大多数C++采用以下三种方法之一管理指针成员:

1.指针成员采取常规指针型行为。这样的类具有指针所有的缺陷但无需特殊的复制控制。

2.类可以实现所谓的“智能指针”行为。指针所指向的对象是共享的,但类能够防止“悬垂指针”。

3.类采取值型行为。指针所指向的对象是唯一的,由每个类对象独立管理。

#include<iostream>
using namespace std;

class HasPtr{
public:
HasPtr(int *p,int i):ptr(p),val(i){}
int *get_ptr()const {return ptr;}
int get_int()const {return val;}
void set_ptr(int *p){ptr=p;}
void set_int(int i){val=i;}
int get_ptr_val()const {return *ptr;}
void set_ptr_val(int val)const {*ptr=val;}
private:
int *ptr;
int val;
};

int main()
{
int obj=0;
HasPtr ptr1(&obj,42);
cout<<ptr1.get_ptr_val()<<'\t'<<ptr1.get_int()<<endl;//0 42
HasPtr ptr2(ptr1);//复制之后,int的值是独立的,而指针的值却纠缠在一起
cout<<ptr2.get_ptr_val()<<'\t'<<ptr2.get_int()<<endl;//0 42
ptr1.set_int(0);
cout<<ptr1.get_int()<<endl;//0
cout<<ptr2.get_int()<<endl;//42
ptr1.set_ptr_val(42);
cout<<ptr2.get_ptr_val()<<endl;//42
return 0;
}

可能出现悬垂指针。

int *ip=new int(42);

HasPtr ptr(ip,10);

delete ip;

ptr.set_ptr_val(0);

这里的问题是ip和ptr中的指针指向同一对象。删除了该对象时,ptr中的指针不再指向有效对象。然而,没有办法得知对象已经不存在了。

定义智能指针的通用技术是采用一个使用计数。智能指针类将一个计算器与类指向的对象相关联。使用计数跟踪该类有多少个对象共享同一指针。使用计数为0时,删除对象。

每次创建类的新对象时,初始化指针并将使用计数置为1。当对象作为另一对象的副本而创建时,复制构造函数复制指针并增加与之相应的使用计数的值。对一个对象进行赋值时,赋值操作符减少左操作数所指对象的使用计数的值(如果使用计数减至0,则删除对象),并增加右操作数所指对象的使用计数的值。最后调用析构函数时,析构函数减少使用计数的值,如果计数减至0,则删除基础对象。

使用计数的一种策略是:需要定义一个单独的具体类用以封装使用计数和相关指针:

class U_Ptr{

friend class HasPtr;

int *ip;

size_t use;

U_Ptr(int *p):ip(p),use(1){}

~U_Ptr(){delete ip;}

};

class HasPtr{

public:

HasPtr(int *p,int i):ptr(new U_Ptr(p)),val(i){}

HasPtr(const HasPtr &orig):ptr(orig.ptr),val(orig.val){++ptr->use;}

HasPtr& operator= (const HasPtr&);

~HasPtr(){if(--ptr->usr==0) delete ptr;}

private:

U_Ptr *ptr;

int val;

};

HasPtr& HasPre::operator=(const HasPtr &rhs)

{

++rhs->use;

if(--ptr->use==0)

delete ptr;

ptr=rhs.ptr;

val=rhs.val;

return *this;

}

为了管理具有指针成员的类,必须定义三个复制控制成员:复制构造函数,赋值操作符,析构函数。这些成员可以定义指针成员的指针型行为或值型行为。

值型类将指针成员所指的基础值的副本给每个对象。复制构造函数分配新元素并从被复制对象处复制值,赋值操作符撤销所保存的原对象并从右操作数向左操作数复制值,析构函数撤销对象。

“智能指针”的类在对象间共享同一基础值,从而提供指针行为。为了实现智能指针的行为,类需要保证基础对象一直存在,直到最后一个副本消失。复制构造函数将指针从旧对象复制到新对象时,会将使用计数加1。赋值操作符将左操作数的使用计数减1并将右操作数的使用计数加1,如果左操作数的使用计数减为0,赋值操作符必须删除它所指向的对象,最后,赋值操作符将指针从右操作数复制到左操作数。析构函数将使用计数减1,并且,如果使用计数减至0,就删除基础对象。

class HasPtr{

public:

HasPtr(const int &p,int i):ptr(new int(p)),val(i){}

HasPtr(const HasPtr &orig):ptr(new int(*orig.ptr)),val(orig.val){}

HasPtr& operator=(const HasPtr&);

~HasPtr(){delete ptr;}

int get_ptr_val()const {return *ptr;}

int get_int()const {return val;}

void set_ptr(int *p){ptr=p;}

void set_int(int i){val=i;}

int *get_ptr()const {return ptr;}

void set_ptr_val(int p)const { *ptr=p;}

private:

int *ptr;

int val;

};

HasPtr& operator=(const HasPtr& rhs)

{

*ptr=*rhs.ptr;

val=rhs.val;

return *this;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: