What is The Rule of Three?
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), part, this is called a member initializer list.)
age(age)
Special member functions
What does it mean to copy a person object?
The main function
shows two distinct copying scenarios. The initialization person 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);b 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
= a
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:
- Changes via
acan
be observed viab. - Once
bis
destroyed,a.nameis
a dangling pointer. - If
ais
destroyed, deleting the dangling pointer yields undefined behavior. - Since the assignment does not take into account what
namepointed
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. Without that check,
= xdelete[] would delete the array containing the source string, because when you write
namex, both
= xthis->name and that.name contain
the same pointer.
Exception safety
Unfortunately, this solution will fail if new throws an exception due to memory exhaustion. One possible solution is to introduce a local variable and reorder the statements:
char[...]
// 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?的更多相关文章
- Salesforce的sharing Rule 不支持Lookup型字段解决方案
Salesforce 中 sharing rule 并不支持Look up 字段 和 formula 字段.但在实际项目中,有时会需要在sharing rule中直接取Look up型字段的值,解决方 ...
- yii2权限控制rbac之rule详细讲解
作者:白狼 出处:http://www.manks.top/yii2_rbac_rule.html 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留 ...
- RBAC中 permission , role, rule 的理解
Role Based Access Control (RBAC)——基于角色的权限控制 permission e.g. creating posts, updating posts role A ro ...
- 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 ...
- SOLID rule in JAVA design.
Classes are the building blocks of your java application. If these blocks are not strong, your build ...
- 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的配 ...
- 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 ...
- yii2权限控制rbac之rule详细讲解(转)
在我们之前yii2搭建后台以及rbac详细教程中,不知道你曾经疑惑过没有一个问题,rule表是做什么的,为什么在整个过程中我们都没有涉及到这张表? 相信我不说,部分人也都会去尝试,或百度或google ...
- 警告 - 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 ...
- 在Salesforce中添加Workflow Rule
在Salesforce中可以添加Workflow Rule来执行特定的动作,比如说:当Object的某个字段发生变化时,根据变化的值去修改其他field,和Trigger的功能很类似,不过Trigge ...
随机推荐
- yum-Remi源配置
Remi repository 是包含最新版本 PHP 和 MySQL 包的 Linux 源,由 Remi 提供维护. 有个这个源之后,使用 YUM 安装或更新 PHP.MySQL.phpMyAdmi ...
- Django基础和基本使用
Django基础 Django是Python下的一款著名的Web框架 框架 任何语言进入到高级部分时,会有认证.session.http.连接数据库等等功能操作,没有框架时需要自己实现 框架 是整个或 ...
- CPU性能分析
CPU性能分析工具 lscpu:查看CPU硬件信息 lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Litt ...
- Runtime之方法
前两篇介绍了类与对象.成员变量&属性&关联对象的相关知识,本篇我们将开始讲解Runtime中最有意思的一部分内容:消息处理机制.我们从一个示例开始. 在OC中,我们使用下面这种方式来调 ...
- Javascript高级编程学习笔记(6)—— 流程控制语句
话不多说,我们直接开始进入今天的主题 流程控制语句 首先什么是流程控制语句呢? 顾名思义,就是控制流程的语句. 在JS中语句定义了ECMAScript中的主要语法,让我们可以使用一系列的关键字来完成指 ...
- Java 大数值类型执行精确计算
简介 如果基本的整数和浮点数精度不能够满足需求,那么可以使用 java.math 包下两个很有用的类:BigInteger 和 BigDecimal.这两个类可以处理包含任意长度数字序列的数值,Big ...
- Kubernetes-2--安装部署
kubernetes的部署方式: 1,kubeadm 2, 二进制安装包部署 条件准备: 1,关闭系统的swap分区 为什么要关闭swap分区,我还没有去看官方的说明,搜索到的答案是 kubernet ...
- 使用 Maven 插件将 class(字节码文件),resource(资源文件),lib(依赖的jar包)分开打包
1. 在pom文件中对各个插件进行配置 <?xml version="1.0" encoding="UTF-8"?> <project xml ...
- 机器学习与Tensorflow(4)——卷积神经网络与tensorflow实现
1.标准卷积神经网络 标准的卷积神经网络由输入层.卷积层(convolutional layer).下采样层(downsampling layer).全连接层(fully—connected laye ...
- django rest framework mixins小结
本篇对drf中的mixins进行简要的分析总结. from rest_framework import viewsets 在这个viewsets中,只有5类Minxin,他们与http方法对应如下: ...