[c++] Copy Control
拷贝控制是什么
C++ allows the programmer to define how objects are to be copied, moved, assigned and destroyed. Together these are known as copy control.
拷贝控制的基础
一、复制初始化
复制初始化 de 使用
Ref: C++的一大误区——深入解释直接初始化与复制初始化的区别
#include <iostream>
#include <cstring>
using namespace std; class ClassTest
{
public:
ClassTest()
{
c[] = '\0';
cout<<"ClassTest()"<<endl;
}
ClassTest& operator=(const ClassTest &ct)
{
strcpy(c, ct.c);
cout<<"ClassTest& operator=(const ClassTest &ct)"<<endl;
return *this;
}
ClassTest(const char *pc)
{
strcpy(c, pc);
cout<<"ClassTest (const char *pc)"<<endl;
}
ClassTest(const ClassTest& ct)
{
strcpy(c, ct.c);
cout<<"ClassTest(const ClassTest& ct)"<<endl;
}
private:
char c[];
};

编译器暗中优化
编译会帮你做很多你看不到,你也不知道的优化,
"你看到的结果,正是编译器做了优化后的代码的运行结果,并不是你的代码的真正运行结果。"
二、访问权限
复制构造函数是可以由编译默认合成的,而且是公有的(public),编译器就是根据这个特性来对代码进行优化的。
如果你自己定义这个复制构造函数,编译则不会自动生成,虽然编译不会自动生成,但是如果你自己定义的复制构造函数仍是公有的话,编译还是会为你做同样的优化。
当它是私有成员时,编译器就会有很不同的举动,因为你明确地告诉了编译器,你明确地拒绝了对象之间的复制操作,所以它也就不会帮你做之前所做的优化,你的代码的本来面目就出来了。
public 的 复制构造函数
#include <iostream>
#include <algorithm>
#include <vector>
#include <string> using namespace std; class CExample
{
private:
int a; public:
CExample(int b) {
a=b;
printf("constructor is called\n");
} CExample(const CExample & c) {
a=c.a;
printf("copy constructor is called\n");
} ~CExample() {
cout<<"destructor is called\n";
} void Show()
{
cout<<a<<endl;
}
}; int main(void)
{
CExample A(); CExample B=A; CExample C=CExample(A); B.Show();
return ;
}
private 的 复制构造函数
编译器明确地拒绝了对象之间的复制操作。
首先,使用指定构造函数创建一个临时对象 (拷贝源),
然后,用复制构造函数将那个临时对象复制到正在创建的对象。
所以,当复制构造函数被声明为私有时,所有的复制初始化都不能使用。
class CExample
{
private:
int a; CExample(const CExample & c)
{
a=c.a;
printf("copy constructor is called\n");
} public:
CExample(int b)
{
a=b;
printf("constructor is called\n");
} ~CExample()
{
cout<<"destructor is called\n";
} void Show()
{
cout<<a<<endl;
}
}; int main(void)
{
CExample A(); >> CExample B=A; >> CExample C=CExample(A); B.Show();
return ;
}
三、 赋值与拷贝控制的区别
Copy Assignment Operator
以下是赋值,但不是初始化。初始化对应的是:copy contructor。
class A {
A& operator=(const A &a) { ... }
/* 类的内部变量一一对应复制 */
}
Copy constructor vs assignment operator
#include<iostream>
#include<stdio.h> using namespace std; class Test
{
public:
Test() {}
Test(const Test &t)
{
cout<<"Copy constructor called "<<endl;
} Test& operator = (const Test &t)
{
cout<<"Assignment operator called "<<endl;
return *this;
}
}; // Driver code
int main()
{
Test t1, t2;
t2 = t1; // assign (不是初始化的过程)
Test t3 = t1; // copy constructor (这是初始化的过程)
return ;
}
Output:
Assignment operator called
Copy constructor called
拷贝控制 and 资源管理
一、实例 Stack
头文件定义
其中的难点:
* move constructor, move assignment
* top函数为何有两个?
#ifndef UB_STACK_H
#define UB_STACK_H
#include <iostream> using namespace std; class UB_stack { public:
UB_stack();
UB_stack(const UB_stack &s);
UB_stack(UB_stack &&s); // <-- move constructor
~UB_stack(); // destructor UB_stack& operator=(const UB_stack &s); // copy assignment
UB_stack& operator=(UB_stack &&s); // <-- move assignment void push(const int &item);
void pop();
int& top();
const int& top() const;
bool empty() const;
bool full() const; private:
class Node; // 节点定义
Node *head_; // 头指针
void reverse(Node *); friend void swap(UB_stack &s1, UB_stack &s2);
}; void swap(UB_stack &s1, UB_stack &s2); // 在类外 依然要声明一次 #endif
类的实现
delete ptr 代表用来释放内存,且只用来释放ptr指向的内存。
delete[] rg 用来释放rg指向的内存,!!还逐一调用数组中每个对象的destructor!!
对于像 int/char/long/int*/struct 等等简单数据类型,由于对象没有destructor,所以用delete 和delete [] 是一样的!但是如果是C++对象数组就不同了!
#include "UB_stack.h"
using namespace std;
class UB_stack::Node {
// allow UB_stack to access private data. 但看上去Node里面也没有private的内容呀!
friend class UB_stack;
// methods and data in this class are private by default.
Node(int i, Node *n = nullptr) : item_{i}, next_{n} {}
~Node() {delete next_; } // destructor cleans up the memory
int item_;
Node *next_;
};
//--------------------------------------------------------------------//
// constructor 但仍然应该是空
UB_stack::UB_stack() : head_{nullptr} {} // copy constructor
UB_stack::UB_stack(const UB_stack &s) : head_{nullptr} {
reverse(s.head_);
}
// move constructor
UB_stack::UB_stack(UB_stack &&s) : head_{s.head_} {
s.head_ = nullptr;
}
//--------------------------------------------------------------------// // destructor
UB_stack::~UB_stack() {
delete head_;
} //--------------------------------------------------------------------//
// return the top of the stack
int& UB_stack::top() {
return head_->item_;
} const int& UB_stack::top() const {
return head_->item_;
} bool UB_stack::empty() const {
return head_ == ;
} bool UB_stack::full() const {
return false;
} // method to work down a given stack
// and push items onto "this" stack correctly
void UB_stack::reverse(Node *h) {
if (h != nullptr) {
reverse(h->next_);
push(h->item_);
}
} // method to push an int onto the stack
void UB_stack::push(const int &item) {
head_ = new Node(item, head_);
}
// 支持了“等号”操作符
UB_stack& UB_stack::operator =(const UB_stack &s) {
// if not already the same stack.
if (this != &s) {
delete head_;
head_ = nullptr;
reverse(s.head_);
}
return *this;
} // Move Assignment.
UB_stack& UB_stack::operator =(UB_stack &&s) {
if (this != &s) {
delete head_;
head_ = s.head_;
s.head_ = nullptr;
}
return *this;
} // pop off the top of the stack.
void UB_stack::pop() {
Node *t = head_;
head_ = head_->next_;
t->next_ = nullptr;
delete t;
} void swap(UB_stack &s1, UB_stack &s2) {
// swap the pointers to the heads of the list only.
// much faster than swapping all the data.
swap(s1.head_, s2.head_);
}
二、内存释放
在main.cpp中,首先不使用指针去 new。
#include <iostream>
#include <vector>
#include "UB_stack.h" using namespace std; int main(void)
{
cout << "Hello world." << endl; UB_stack s;
s.push();
s.push();
s.push(); s.pop();
cout << s.top() << endl;
// 虽然没有delete,但系统自动调用了~UB_stack()
return ;
}
改为 new后的指针方式,则调用 delete才会析构。
int main(void)
{
UB_stack *s = new UB_stack();
s->push();
s->push();
s->push();
s->pop();
cout << s->top() << endl;
delete s; return ;
}
三、拷贝构造、移动构造
深拷贝 & 浅拷贝
拷贝初始化、赋值过程的区别。
// 浅拷贝
UB_stack::UB_stack(const UB_stack &s) : head_{s.head_} { }
// 深拷贝
UB_stack::UB_stack(const UB_stack &s) : head_{nullptr} {
reverse(s.head_);
} UB_stack s2 {s1};
// 浅拷贝
UB_stack& UB_stack::operator=(const UB_stack &s) {
head_ = s.head_;
return *this;
}
// 深拷贝
UB_stack& UB_stack::operator=(const UB_stack &s) {
if (this != &s) {
delete head_;
head_ = nullptr;
reverse(s.head_);
}
return *this;
} s2 = s1
移动构造
Move Semantics.

可见,偷走了临时变量的内存空间,据为己用。节省了开辟空间的时间。
/* 思路:
* 新类截取旧类的指针head_
* 然后,迫使旧类放弃head_
*/ // move constructor.
UB_stack::UB_stack(UB_stack &&s) : head_{s.head_} {
s.head_ = nullptr;
} // move assignment.
UB_stack& UB_stack::operator =(UB_stack &&s) {
if (this != &s) {
delete head_;
head_ = s.head_;
s.head_ = nullptr;
}
return *this;
}
std:move()
一、基本原理
noexcept
void except_func() noexcept; 表示不抛出异常。
void except_func() noexcept (常量表达式);表达式为true不会抛出异常;flase抛出异常。
”右值“ 特性
Ref: https://www.zhihu.com/question/22111546/answer/30801982
右值中的数据可以被安全移走这一特性使得右值被用来表达移动语义。
以同类型的右值构造对象时,需要以引用形式传入参数。右值引用顾名思义专门用来引用右值,左值引用和右值引用可以被分别重载,这样确保左值和右值分别调用到拷贝和移动的两种语义实现。
对于左值,如果我们明确放弃对其资源的所有权,则可以通过std::move()来将其转为右值引用。std::move()实际上是static_cast<T&&>()的简单封装。
二、移动语义的好处
没有移动语义
以表达式的值(例为函数调用)初始化对象或者给对象赋值是这样的: (重点:红色字体)
vector<string> str_split(const string& s);
vector<string> v = str_split("1,2,3"); // 返回的vector用以拷贝构造对象v。为v申请堆内存,复制数据,然后析构临时对象(释放堆内存)。
vector<string> v2;
v2 = str_split("1,2,3"); // 返回的vector被复制给对象v(拷贝赋值操作符)。需要先清理v2中原有数据,将临时对象中的数据复制给v2,然后析构临时对象。
注:v的拷贝构造调用有可能被优化掉,尽管如此在语义上仍然是有一次拷贝操作。
支持移动语义
同样的代码,在支持移动语义的世界里就变得更美好了,可以接收右值表达式。
vector<string> str_split(const string& s);
vector<string> v = str_split("1,2,3"); // 返回的vector用以移动构造对象v。v直接取走临时对象的堆上内存,无需新申请。之后临时对象成为空壳,不再拥有任何资源,析构时也无需释放堆内存。
vector<string> v2;
v2 = str_split("1,2,3"); // 返回的vector被移动给对象v(移动赋值操作符)。先释放v2原有数据,然后直接从返回值中取走数据,然后返回值被析构。
注:v的移动构造调用有可能被优化掉,尽管如此在语义上仍然是有一次移动操作。
不用多说也知道上面的形式是多么常用和自然。而且这里完全没有任何对右值引用的显式使用,性能提升却默默的实现了。
三、触发"移动构造"
Ref: c++ 之 std::move 原理实现与用法总结
std::move函数可以以非常简单的方式将左值引用转换为右值引用。Goto: (左值 右值 引用 左值引用) 概念
(1) C++ 标准库使用比如vector::push_back 等这类函数时,会对参数的对象进行复制,连数据也会复制.这就会造成对象内存的额外创建,本来原意是想把参数push_back进去就行了,通过std::move,可以避免不必要的拷贝操作。
(2) std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝所以可以提高利用效率,改善性能。
(3) 对指针类型的标准库对象并不需要这么做。
按值传入参数
(1) 初始化
这里的序列容器,默认采用的是拷贝构建。这里通过move变为移动构造。
#include <iostream>
#include <vector>
#include <list>
#include <chrono> using namespace std; int main()
{
std::vector<int> vecInt = {,,}; // 因为这里是左值
std::vector<int> vecCopy = std::move(vecInt); // 所以这里本来也会是”左值“的角色,也就无法触发"move construct",要触发,就通过std::move后,左变右
vecCopy.at() = ; for (auto x: vecInt)
{
cout << x << endl;
} return ;
}
(2) 插入
void push_back( const T& value ); // (1) 左值调用,版本一
void push_back( T&& value ); // (2) 右值调用,版本二
如果你要往容器内放入超大对象,那么版本二自然是最佳选择。
vector<vector<string>> vv;
vector<string> v = {"", ""};
v.push_back(""); // 临时构造的string类型右值被移动进容器v
vv.push_back(std::move(v)); // 显式将v移动进vv, v中便没有了内容,move给了vv.
这里的参数类型通过 move,由 "左值调用" --> "右值调用",这叫做:Argument-Dependent Lookup (ADL) 参数依赖查找
它的规则就是当编译器对无限定域的函数调用进行名字查找时,除了当前名字空间域以外,也会把函数参数类型所处的名字空间 加入查找的范围。
(3) 构造实现
这里的自定义类,通过参数的”左右“类型,从而调用不同的构造函数。
class People {
public:
People(string name) // 按值传入字符串,可接收左值、右值。接收左值时为复制,接收右值时为移动
: name_(std::move(name)) // 显式移动构造,将传入的字符串移入成员变量
{
}
string name_;
};
People a("Alice"); // 移动构造 name,这是个”右值“。
string bn = "Bob";
People b(bn); // 拷贝构造 name,这是个”左值“
按值返回结果
vector<string> str_split(const string& s) {
vector<string> v;
// ...
return v; // v是左值,但优先移动,不支持移动时仍可复制。
}
四、类中实现
容器的浅拷贝构造
使用:UB_stack s2 = std::move(s1);
UB_stack::UB_stack(UB_stack &&s) noexcept : head_{std::move(s.head_)} {
}

容器的真搬运构造
使用:UB_stack s2 = std::move(s1);
UB_stack::UB_stack(UB_stack &&s) : head_{std::move(s.head_)} {
s.head = nullptr;
}

容器的浅拷贝赋值
s2 = std::move(s1);
UB_stack& UB_stack::operator=(UB_stack &&s) noexcept {
head_ = std::move(s.head_);
return *this;
}

- 容器的真搬运赋值
s2 = std::move(s1);
UB_stack& UB_stack::operator=(UB_stack &&s) {
if (this != &s) {
delete head_;
head_ = std::move(s.head_);
s.head_ = nullptr;
}
return *this;
}

End.
[c++] Copy Control的更多相关文章
- Copy Control settings
Copy Control settings Skip to end of metadata Created by Rajesh Banka, last modified by Jyoti ...
- [C++] Copy Control (part 1)
Copy, Assign, and Destroy When we define a class, we specify what happens when objects of the class ...
- C/C++:copy control (拷贝控制)
前言:当定义一个类的时候,我们显示或者隐式地指定在此类型的对象拷贝,移动,赋值,销毁时做些什么,一个类通过定义五种特殊的成员函数来控制这些操作,包括拷贝构造函数,拷贝赋值运算符,移动构造函数,移动赋值 ...
- C++之拷贝控制 (Copy Control)
只有2种成员 值成员: 指针成员: 依实现可分为raw pointer / shared_ptr; 现在,仅考虑第③种:资源对象共享 角度来考虑拷贝控制 类的两种语义:值语义.似指针 编译器提供的de ...
- 【C++ 补习】Copy Control
C++ Primer 5th edition, chapter 13. The Rule of Three If a class needs a destructor, it almost surel ...
- Bug 14143011 : ORA-19606: CANNOT COPY OR RESTORE TO SNAPSHOT CONTROL FILE
Bug 14143011 : ORA-19606: CANNOT COPY OR RESTORE TO SNAPSHOT CONTROL FILE [oracle@test]$ tail -f rma ...
- C++-copy constructor、copy-assignment operator、destructor
本文由@呆代待殆原创,转载请注明出处. 对于一个类来说,我们把copy constructor.copy-assignment operator.move constructor.move-assig ...
- [c++] Smart Pointers
内存管理方面的知识 基础实例: #include <iostream> #include <stack> #include <memory> using names ...
- code of C/C++(3) - 从 《Accelerated C++》源码学习句柄类
0 C++中多态的概念 多态是指通过基类的指针或者引用,利用虚函数机制,在运行时确定对象的类型,并且确定程序的编程策略,这是OOP思想的核心之一.多态使得一个对象具有多个对象的属性.class Co ...
随机推荐
- JQuery对象与DOM对象的区别与转换
1.jQuery对象和DOM对象的区别 DOM对象,即是我们用传统的方法(javascript)获得的对象,jQuery对象即是用jQuery类库的选择器获得的对象; eg: var domObj ...
- siteserver cms选择栏目搜索无效
标签必须以空格分开,且option 的value必须给id不能给名称
- centos中yum安装mysql路径
1. 使用命令service mysqld stop 停止mysql 查看mysql数据库的默认路径:/var/lib/mysql 使用cp -afir /var/lib/mysql/* /us ...
- 线上应用bug跟踪查找-友盟统计
线上的应用只要用心点点都能发现些bug,连微信,QQ也不列外.但是bug中最严重的算是闪退了,这导致了用户直接不能使用我们的app. 我们公司是特别注重用户反馈和体验的,我们会定期打电话咨询用户的使用 ...
- PHP的函数应用
1.全部变量 全局变量也称为外部变量,是在函数的外部定义的,它的作用域为从变量定义处开始,到本程序文件的结尾.和其他编程语言不同,全局变量不是自动设置为可用的.在PHP中,由于函数可以视为单独的程序片 ...
- ajax优点与缺点
ajax的优点 Ajax的给我们带来的好处大家基本上都深有体会,在这里我只简单的讲几点: 1.最大的一点是页面无刷新,在页面内与服务器通信,给用户的体验非常好. 2.使用异步方式与服务器通信,不需要打 ...
- 删除Mysql数据表中多余的重复记录的sql语句
数据表 sniper_tb 中存在主键 id,字段url,现需要在url字段上添加 unique,但由于url存在重复记录,导致添加失败. 如何删除表中多余的url重复记录,仅保持一条? 思路一 将 ...
- 探索c#之不可变数据类型
阅读目录: 不可变对象 自定义不可变集合 Net提供的不可变集合 不可变优点 不可变对象缺点 不可变对象 不可变(immutable): 即对象一旦被创建初始化后,它们的值就不能被改变,之后的每次改变 ...
- 字符串匹配算法 - KMP
前几日在微博上看到一则微博是说面试的时候让面试者写一个很简单的字符串匹配都写不出来,于是我就自己去试了一把.结果写出来的是一个最简单粗暴的算法.这里重新学习了一下几个经典的字符串匹配算法,写篇文章以巩 ...
- TDR分辨率
在日常的生活工作中,有很多测试测量的工具,比如测量长度的尺子,计量时间的钟表等等,谈到测试测量工具的时候,分辨率是关键指标之一,比如尺子的 分辨率是1mm,时钟的分辨率是秒.所谓分辨率就是测试测量工具 ...