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. unique(未完成)

    const unique = arr => { const sortedArr = arr.sort((a, b) => a > b); const first = sortedAr ...

  2. day13_雷神_前端01

    #前端 html 服务器端返回的就是一个字符串,浏览器根据html规则去渲染这个字符串. html 是超文本标记语言,相当于定义统一的一套规则,大家都遵守它,这样就可以让浏览器根据标记语言的规则去解释 ...

  3. Rabbit RPC 代码阅读(一)

    前言 因为想对RPC内部的机制作一个了解,特作以下阅读代码日志,以备忘. RPC介绍 Rabbit RPC 原理可以用3点概括: 1.服务端启动并且向注册中心发送服务信息,注册中心收到后会定时监控服务 ...

  4. LOJ#6387 「THUPC2018」绿绿与串串 / String (Manacher || hash+二分)

    题目描述 绿绿和 Yazid 是好朋友.他们在一起做串串游戏. 我们定义翻转的操作:把一个串以最后一个字符作对称轴进行翻转复制.形式化地描述就是,如果他翻转的串为 RRR,那么他会将前 ∣R∣−1个字 ...

  5. Android开发工程师文集-Android知识点讲解

    前言 大家好,给大家带来Android开发工程师文集-Android知识点讲解的概述,希望你们喜欢 WebView讲解 一般通过Intent调用系统的浏览器: Uri uri = Uri.parse( ...

  6. ElasticSearch权威指南学习(分布式文档存储)

    路由文档到分片 当你索引一个文档,它被存储在单独一个主分片上.Elasticsearch是如何知道文档属于哪个分片的呢?当你创建一个新文档,它是如何知道是应该存储在分片1还是分片2上的呢? 进程不能是 ...

  7. MANIFEST.MF文件详解

    1. 依赖包是否在classpath中: 2. 资源文件目录是否在classpath中: 3. 主类是否正确: 具体配置参考 maven-jar-plugin 配置 <plugin> &l ...

  8. 【微服务】.netCore eShopOnContainers 部署实践《二》

    Docker 专业术语介绍 优点:轻量级.可伸缩(灵活性).可靠性.可移植  Container image A package with all of the dependencies and in ...

  9. thinkpad的E480安装ubuntu后wifi无法使用问题解决

    买了新电脑,安装ubuntu新系统之后,遇到了一个比较麻烦的问题,在ubuntu中,无法使用wifi. 用新产品就是要当小白鼠啊,查了一下资料,发现这个使用的rtl8821ce的wifi芯片,该wif ...

  10. java-null简介

    对于每一个Java程序员来说,null肯定是一个让人头痛的东西,连Java的发明者都承认这是一项巨大的设计失误,今天就来总结一下Java中关于null的知识. 1.null不属于任何类型,可以被转换成 ...