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 ...
随机推荐
- 1.2万事开头hello world+交互+getpass、sys模块初识
1.python的hello world: ①运行cmd-输入python-输入print (“hello world!”) ②创造.py的文本helloworld.py(后缀是为了告诉其他人)-输入 ...
- MySQL insert语句锁分析
最近对insert的锁操作比较费解,所以自己动手,一看究竟.主要是通过一下三个sql来看一下执行中的sql的到底使用了什么锁. select * from information_schema.INN ...
- Visual Studio 2017中使用正则修改部分内容
最近在项目中想实现一个小工具,需要根据类的属性<summary>的内容加上相应的[Description]特性,需要实现的效果如下 修改前: /// <summary> /// ...
- Java 实现 Http 请求工具类
package com.demo.util; import java.io.BufferedReader; import java.io.IOException; import java.io.Inp ...
- JS应用实例1:注册页面表单校验
这里是简单的前端校验,后边还会介绍后台的校验. 多重校验保证获取安全数据 步骤: 1.确定事件onsubmit,并绑定函数 2.编写函数,作用是获取输入数据 3.判断数据是否合法,合法则提交,否则表单 ...
- LabVIEW(十四):VI属性
1.VI的属性在项目研究中是很常用的,但是会经常忽略VI的属性设置,建议在编程事常常运用这些属性的设置,这样就可以使你的程序更加完善,易懂性也会提高. 属性快捷键:Ctrl+I. 2.常规 (1).编 ...
- php开发中应该注意的错误开关与常见处理[开发篇]
我们可能一开始就接触一个项目的开发,刚开始时都是信心满满,一定把这个项目做得非常完美,但是时间那么少,任务那么多,我们就只有将就了. 首先,一般情况下,我们会加一个调试标志,define('APP_D ...
- HttpRunnerManager接口测试平台部署在服务器上(Centos + python3.6 + Mysql5.7 + uwsgi + nginx)
整个思路:HttpRunnerManager + python3.6 + Mysql5.7 + uwsgi + nginx 安装依赖环境,将源代码传到服务器,修改settings,增加uwsgi配置, ...
- jsp-9大内置对象简介
产生背景 JSP引擎在调用JSP对应的jspServlet时,会传递或创建9个与web开发相关的对象供jspServlet使用.JSP技术的设计者为便于开发人员在编写JSP页面时获得这些web对象的引 ...
- 课程四(Convolutional Neural Networks),第三 周(Object detection) —— 2.Programming assignments:Car detection with YOLOv2
Autonomous driving - Car detection Welcome to your week 3 programming assignment. You will learn abo ...