Question:

What does copying
an object mean? What are the copy
constructor and the copy
assignment operator? When do I need to declare them myself? How can I prevent my objects from being copied?

Answer:

Introduction

C++ treats variables of user-defined types with value semantics. This means that objects are implicitly copied in various contexts, and we should understand what "copying an object" actually
means.

Let us consider a simple example:

class person
{
std::string name;
int age; public: person(const std::string& name, int age) : name(name), age(age)
{
}
}; int main()
{
person a("Bjarne Stroustrup", 60);
person b(a); // What happens here?
b = a; // And here?
}

(If you are puzzled by the name(name),
age(age)
 part, this is called a member initializer list.)

Special member functions

What does it mean to copy a person object?
The main function
shows two distinct copying scenarios. The initialization person
b(a);
 is performed by the copy constructor. Its job is to construct a fresh object based on the state of an existing object. The assignment b
= a
 is performed by thecopy assignment operator. Its job is generally a little more complicated, because the target object is already in some valid state that needs to be dealt
with.

Since we declared neither the copy constructor nor the assignment operator (nor the destructor) ourselves, these are implicitly defined for us. Quote from the standard:

The [...] copy constructor and copy assignment operator, [...] and destructor are special member functions. [ Note: The
implementation will implicitly declare these member functions for some class types when the program does not explicitly declare them. The implementation will implicitly define them if they are used. [...] end
note ] [n3126.pdf section 12 §1]

By default, copying an object means copying its members:

The implicitly-defined copy constructor for a non-union class X performs a memberwise copy of its subobjects. [n3126.pdf section 12.8 §16]

The implicitly-defined copy assignment operator for a non-union class X performs memberwise copy assignment of its subobjects. [n3126.pdf section 12.8 §30]

Implicit definitions

The implicitly-defined special member functions for person look
like this:

// 1. copy constructor
person(const person& that) : name(that.name), age(that.age)
{
} // 2. copy assignment operator
person& operator=(const person& that)
{
name = that.name;
age = that.age;
return *this;
} // 3. destructor
~person()
{
}

Memberwise copying is exactly what we want in this case: name and age are
copied, so we get a self-contained, independent person object.
The implicitly-defined destructor is always empty. This is also fine in this case since we did not acquire any resources in the constructor. The members' destructors are implicitly called after the person destructor
is finished:

After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X's direct [...] members [n3126.pdf 12.4 §6]

Managing resources

So when should we declare those special member functions explicitly? When our class manages a resource, that is, when an object of the class is responsible for
that resource. That usually means the resource is acquired in the constructor (or passed into the constructor) and released in
the destructor.

Let us go back in time to pre-standard C++. There was no such thing as std::string,
and programmers were in love with pointers. The person class
might have looked like this:

class person
{
char* name;
int age; public: // the constructor acquires a resource:
// in this case, dynamic memory obtained via new[]
person(const char* the_name, int the_age)
{
name = new char[strlen(the_name) + 1];
strcpy(name, the_name);
age = the_age;
} // the destructor must release this resource via delete[]
~person()
{
delete[] name;
}
};

Even today, people still write classes in this style and get into trouble: "I pushed a person into a vector and now I get crazy memory errors!" Remember that by default, copying an object
means copying its members, but copying the name member
merely copies a pointer, not the character array it points to! This has several unpleasant effects:

  1. Changes via a can
    be observed via b.
  2. Once b is
    destroyed, a.name is
    a dangling pointer.
  3. If a is
    destroyed, deleting the dangling pointer yields undefined behavior.
  4. Since the assignment does not take into account what name pointed
    to before the assignment, sooner or later you will get memory leaks all over the place.

Explicit definitions

Since memberwise copying does not have the desired effect, we must define the copy constructor and the copy assignment operator explicitly to make deep copies of the character array:

// 1. copy constructor
person(const person& that)
{
name = new char[strlen(that.name) + 1];
strcpy(name, that.name);
age = that.age;
} // 2. copy assignment operator
person& operator=(const person& that)
{
if (this != &that)
{
delete[] name;
// This is a dangerous point in the flow of execution!
// We have temporarily invalidated the class invariants,
// and the next statement might throw an exception,
// leaving the object in an invalid state :(
name = new char[strlen(that.name) + 1];
strcpy(name, that.name);
age = that.age;
}
return *this;
}

Note the difference between initialization and assignment: we must tear down the old state before assigning to name to
prevent memory leaks. Also, we have to protect against self-assignment of the form x
= x
. Without that check, delete[]
name
 would delete the array containing the source string, because when you write x
= x
, both this->name and that.name contain
the same pointer.

Exception safety

Unfortunately, this solution will fail if new
char[...]
 throws an exception due to memory exhaustion. One possible solution is to introduce a local variable and reorder the statements:

// 2. copy assignment operator
person& operator=(const person& that)
{
char* local_name = new char[strlen(that.name) + 1];
// If the above statement throws,
// the object is still in the same state as before.
// None of the following statements will throw an exception :)
strcpy(local_name, that.name);
delete[] name;
name = local_name;
age = that.age;
return *this;
}

This also takes care of self-assignment without an explicit check. An even more robust solution to this problem is the copy-and-swap
idiom
, but I will not go into the details of exception safety here. I only mentioned exceptions to make the following point: Writing classes that manage resources is hard.

Noncopyable resources

Some resources cannot or should not be copied, such as file handles or mutexes. In that case, simply declare the copy constructor and copy assignment operator as private without
giving a definition:

private:

    person(const person& that);
person& operator=(const person& that);

Alternatively, you can inherit from boost::noncopyable or
declare them as deleted (C++0x):

person(const person& that) = delete;
person& operator=(const person& that) = delete;

The rule of three

Sometimes you need to implement a class that manages a resource. (Never manage multiple resources in a single class, this will only lead to pain.) In that case, remember the rule of three:

If you need to explicitly declare either the destructor, copy constructor or copy assignment operator yourself, you probably need to explicitly declare all three of them.

(Unfortunately, this "rule" is not enforced by the C++ standard or any compiler I am aware of.)

Advice

Most of the time, you do not need to manage a resource yourself, because an existing class such as std::string already
does it for you. Just compare the simple code using a std::string member
to the convoluted and error-prone alternative using a char* and
you should be convinced. As long as you stay away from raw pointer members, the rule of three is unlikely to concern your own code.

What is The Rule of Three?的更多相关文章

  1. Salesforce的sharing Rule 不支持Lookup型字段解决方案

    Salesforce 中 sharing rule 并不支持Look up 字段 和 formula 字段.但在实际项目中,有时会需要在sharing rule中直接取Look up型字段的值,解决方 ...

  2. yii2权限控制rbac之rule详细讲解

    作者:白狼 出处:http://www.manks.top/yii2_rbac_rule.html 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留 ...

  3. RBAC中 permission , role, rule 的理解

    Role Based Access Control (RBAC)——基于角色的权限控制 permission e.g. creating posts, updating posts role A ro ...

  4. jquery validate minlength rule is not working

    Question: I have a form with a password field. The password has to be at least 8 characters long. &l ...

  5. SOLID rule in JAVA design.

    Classes are the building blocks of your java application. If these blocks are not strong, your build ...

  6. make[2]: *** No rule to make target `/root/.pyenv/versions/anaconda3-2.4.0/lib/libpython3.5m.so', needed by `evaluation.so'. Stop.

    当出现No rule to make target ,肯定是Makefile有问题. 有的makefile是脚本生成的,你得看脚本的配置文件对不对. 我的是这个脚本生成的.发现是Pythondir的配 ...

  7. AASM rule of scoring sleep stages using EEG signal

    Reference: AASM (2007). The AASM Manual for the Scoring of Sleep and Associated Events: Rules, Termi ...

  8. yii2权限控制rbac之rule详细讲解(转)

    在我们之前yii2搭建后台以及rbac详细教程中,不知道你曾经疑惑过没有一个问题,rule表是做什么的,为什么在整个过程中我们都没有涉及到这张表? 相信我不说,部分人也都会去尝试,或百度或google ...

  9. 警告 - no rule to process file 'WRP_CollectionView/README.md' of type net.daringfireball.markdown for architecture i386

    warning: no rule to process file '/Users/mac/Downloads/Demo/Self/WRP_CollectionView/WRP_CollectionVi ...

  10. 在Salesforce中添加Workflow Rule

    在Salesforce中可以添加Workflow Rule来执行特定的动作,比如说:当Object的某个字段发生变化时,根据变化的值去修改其他field,和Trigger的功能很类似,不过Trigge ...

随机推荐

  1. xpath和lxml类库

    1. xpath和lxml lxml是一款高性能的 Python HTML/XML 解析器,我们可以利用XPath,来快速的定位特定元素以及获取节点信息 2. 什么是xpath XPath (XML ...

  2. Javascript学习之:JSON

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.它是基于ECMAScript的一个子集,采用完全独立于语言的文本格式.这些特性使JSON成为理想的数据交换 ...

  3. C++回调:利用Sink

    Sink的本质是利用C++的封装.继承.多态的面向对象来实现,从实现角度来说,更优于函数指针回调: // cbBysink.cpp : Defines the entry point for the ...

  4. Flask 中内置的 Session

    Flask中的Session Flask中的Session不同于Django的session,django的session存在后端数据库中,而flask的session会将你的SessionID存放在 ...

  5. IPv6技术详解:基本概念、应用现状、技术实践(上篇)

    本文来自微信技术架构部的原创技术分享. 1.前言 普及IPV6喊了多少年了,连苹果的APP上架App Store也早已强制IPV6的支持,然并卵,因为历史遗留问题,即使在IPV4地址如果饥荒的情况下, ...

  6. Java面试集合(一)

    前言 大家好,给大家带来Java面试集合(一)的概述,希望你们喜欢 一 1.Java按应用范围可划分几个版本? 答:Java按应用范围有三个版本,分别是JavaSE,JavaEE,JavaME. 2. ...

  7. jdk-tomcat-jenkens 安装

    1--安装JDK 下载JDK放到你需要的目录,解压,然后添加环境变量 2--安装tomcat 从官方网站下载tomcat的安装包,然后解压 启动tomcat , TOMCAT的默认端口是8080,要记 ...

  8. HttpClient实现HTTP文件通用下载类

    import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import org.apache. ...

  9. 机器学习技法笔记:02 Dual Support Vector Machine

    Roadmap Motivation of Dual SVM Lagrange Dual SVM Solving Dual SVM Messages behind Dual SVM Summary

  10. 机器学习与Tensorflow(4)——卷积神经网络与tensorflow实现

    1.标准卷积神经网络 标准的卷积神经网络由输入层.卷积层(convolutional layer).下采样层(downsampling layer).全连接层(fully—connected laye ...