C++ Primer 总结之Chap13 Copy Control

Copy Control : the copy constructor, assignment opertor and destructor of class.

  1. Copy contructor is a special constructor that takes a single parameter that is a (usually const) reference(必须是引用,否则会报错,无穷递归) to the class type.

    It is used explicitly when we initialize a new object from an existing object of the same type, and implicity when we pass or return objects(nonreference) of that type to or from functions.

  2. C++ supports 2 forms of initialization: direct and copy. Direct-initialization places the initializer in parentheses, while Copy-initialization uses the = symbol.

    【注意】:=在初始化和赋值时是不一样的。初始化时是先用右边的构造函数建立一个临时对象,再用该对象对左边的进行拷贝初始化(当然,现在很多编译器都进行优化,直接在左边进行构造,跳过了拷贝构造这一步;而赋值的时候是调用类中的赋值操作符成员函数(若未自定义则系统自动生成,将所有成员进行复制;系统自动生成的拷贝构造函数也是全员复制进行初始化)。

    但无论如何,初始化始终是会调用构造函数的,尽管出现了=,也不是调用赋值操作符。

    string empty_copy = string(); // copy-init
    string empty_direct; // direct-init
    string dots(10, ','); // direct-init
    string commas = ",,,"; // copy-init
    string commas2(commas); // copy-init
    
    
  3. 一般来说,当有成员是指针时,此类的拷贝构造函数就需要自定义(赋值操作符也需要重载)。

    若使用系统自动生成的,则在复制后会出现:

    • 二者的指针内容相同,指向同一个对象,这可能不是我们实际想要的效果

    • 删除其中一个成员的指针后,会出现空悬指针( dangling pointer ),然后再删除另一个成员的指针就会出错啦,读写访问错误之类的。

    class Name{
    public:
        Name(string *tmp = new string)pString(tmp){} // 必须是new,保证全局,防止空悬指针
        Name(const Name& tmp):pString(new string(*tmp.pString)){} // deep-copy
        ~Name(){delete pString;}
        
        string *pString; // public for convenience of testing
    }
    
    Name a, b(a); // a,b 的 pString 完全是两个东西
    

    若无上面这个自定义的拷贝构造函数,则 a,bpString 指向的是同一个东西,那么在执行的时候,一旦到了调用析构函数的时候,则b析构时已经删除指针了,但a析构还要再删除一遍空悬指针,这就出错了。

    当然,若不自定义析构函数进行delete操作,当然也不会报错,但这就导致内存泄漏了,包括该未被删除的对象所占用的一切资源都不会被释放!

  4. 若一个类本应实现 copy control 而却没有实现,会带来严重的后果。在传入传出函数、作为容器元素时都会触发默认的拷贝构造函数,从而导致各种意料不到的结果。所以当不知道用不用的时候,先禁用拷贝构造和赋值操作符,这样在触发默认的时候会出现编译错误,但至少这是安全的,可知的。

    To prevent copies, a class must explicitly declare its copy constructor as private. However, the friends and members of that class can still make copies, we can prevent it by declaring but not defining it.

  5. The assignment operator: a member of class named operator=, whose return type is reference to the class type itself.

    Most overloaded operator may be defined as member of nonmember functions. The number of its parameters is the same as the number of its operands(including this,which is bound to the first parameter, if the operator is a member)

  6. Destructor 用来释放资源,不管是否定义自己的 Destructor,实际上编译器都会自动生成一个并且会执行(或者说其代码追加在自定义的 Destructor 后边)。所以我们不用刻意去析构每一个成员,只做需要额外处理的事情就好了。

    Destructor is only called when a pointer to a dynamically allocated(new) object is deleted or when an actual object(not reference or pointer) goes out of scope. We can provide only one destructor.

    class K
    {
        K();
        ~K(){cout << "Destructor called";}
    };
    
    int main(){
        
        {
            K k1; // Destructor called
            K *k2 = &k1;
            delete k2; // error,非new的pointer不可delete
            K *k3 = new K(); // Destructor not called if do not delete k3 explicitly
        }
        
        return 0;
    }
    
    
  7. 考虑一个文件归类的例子:一个文件可以属于多个文件夹,而一个文件夹里有多个文件。提供文件到文件夹的添加和移除操作。

    最开始的时候是想,在每个文件夹里建立set<File>结构存放,但其实set<File*>会更好一些,没有必要复制文件到全部包含该文件的文件夹中,只要有一份,再用地址来控制就好了——很简单的思路,但产生的效率差异巨大。

    同时文件中也可以有一个set<Folder*>结构,表示包含该文件的文件夹。具体实现如下,需要注意的点有:

    • 由于部分操作重复出现,此时应将其写成一个 private utility function,避免代码无意义的重复,在这里就体现为 put_Msg_into_Folders, rmv_Msg_from_Folders 这两个,它们在三个 Copy Control 函数里会重复用到
    • 在进行赋值操作符重载时,应当注意避免自赋值(self-assignment),在此处是通过判断对象地址实现的:if(this != &rhs){//blablabla}
class Message;

class Folder {
public:
	void addMsg(Message*);
	void rmvMsg(Message*);
private:
	set<Message*> msges;
	
};

class Message {
public:
	Message(const string &str = "") :content(str) {} // default constructor
	Message(const Message&); // copy constructor

	Message& operator=(const Message &rhs); // assignment operator
	~Message(); // destructor

	void save(Folder&);
	void remove(Folder&);

private:
	string content;
	set<Folder*> folders;
	void put_Msg_into_Folders();
	void rmv_Msg_from_Folders();
};

Message::Message(const Message &rhs) : content(rhs.content), folders(rhs.folders){
	put_Msg_into_Folders();
}

Message::~Message() {
	rmv_Msg_from_Folders();
}

Message& Message::operator=(const Message &rhs) {
	if (&rhs != this) { // not rhs != (*this)
		content = rhs.content;
		rmv_Msg_from_Folders();
		folders = rhs.folders;
		put_Msg_into_Folders();
	}
	return *this;
}

void Message::put_Msg_into_Folders() {
	for (set<Folder*>::const_iterator it = folders.begin(); it != folders.end(); ++it) {
		(*it)->addMsg(this);
	}
}

void Message::rmv_Msg_from_Folders() {
	for (set<Folder*>::const_iterator it = folders.begin(); it != folders.end(); ++it) {
		(*it)->rmvMsg(this);
	}
}

void Message::save(Folder &folder) {
	folders.insert(&folder);
	folder.addMsg(this);
}

void Message::remove(Folder &folder) {
	folders.erase(&folder);
	folder.rmvMsg(this);
}

void Folder::addMsg(Message* msg) {
	msges.insert(msg);
}

void Folder::rmvMsg(Message* msg) {
	msges.erase(msg);
}    
    
  1. Return or change the object by the pointer member is ok even in a const member function. However, you can not change the pointer itself in that case.

  2. 遇到指针成员时有三种处理方式:

    • 深拷贝
    • 浅拷贝
    • 智能指针,the object to which the pointers point is shared, but the class prevents dangling pointers
  3. Smart Pointer :主要通过引用计数,只在计数为0时进行删除指针的操作。

    一种实现的方法是通过一个中间类来实现控制。

    class U_ptr{
        friend class HasPtr; // Access to this private class
        size_t cnt;
        int *p;
        U_ptr(int *tmp):p(tmp), cnt(1){} // 每次新对象都先计数为1,被复制再增加计数
        ~U_ptr(){delete p;}
    };
    
    class HasPtr{
    public:
        HasPtr(int *p):ptr(new U_ptr(p)){}
        HasPtr(const HasPtr &orig):ptr(orig.ptr){++ptr->cnt;}
        HasPtr& operator=(const HasPtr &rhs){
            HasPtr tmp(rhs); // 右引用计数加一
            swap(tmp);
            return *this; // 函数结束后,tmp 自然会调用析构函数,原引用计数会减一
        }
        ~HasPtr(){if(--ptr->cnt == 0) delete ptr;}
    private:
        U_ptr *ptr;
    };
    
    HasPtr hasPtr(new int(3)); // 必须从堆上分配,不能使用局部对象的地址
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值