[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 ...
随机推荐
- oracle遍历表更新另一个表(一对多)
declare cursor cur_test is select t.txt_desig, m.segment_id, s.code_type_direct, case when s.uom_dis ...
- JS 的事件委托机制
以前写上图所示的鼠标点击触发事件,一般都是用如下所示的给每一个表示列表的标签绑定一个click事件(演示用的例子的框架是React): 毫无疑问,这样是比较繁琐的,以后维护修改改个函数名什么的还不方便 ...
- [原创]MySQL RR隔离级别下begin或start transaction开启事务后的可重复读?
Server version: 5.6.21-log MySQL Community Server (GPL) 前提提要: 我们知道MySQL的RR(repeatable read)隔 ...
- 下订单存储过程 - MYSQL
BEGIN DECLARE smark INT; DECLARE orderId INT; /*查询课程是否存在,如果不存在就不执行订单操作了*/ ) FROM t_course WHERE id = ...
- Lua游戏时区问题
关于cocos2dx-lua版本中游戏时间显示问题 2015-04-19 19:07 1466人阅读 评论(0) 收藏 举报 分类: Lua(29) cocos2d(38) 版权声明:本文为博 ...
- Angular 1.x 升级到 Angular 2
原项目用ng1.5写的,现在改成ng2.0了,踩了不少坑,不过都忘记了. 如果你也正好要做这个工作,正好看到这个文章,不妨参考下. AngularJs 1.x -> 2.0 ng-repeat ...
- 在SharePoint 2010中,如何找回丢失的服务账号(Service Account)密码
背景信息: 通常在SharePoint环境中我们会使用很多的服务账号来运行各种不同的服务,尤其在企业环境中,由于权限管理条例严格,这些服务账号更是只能多不能少.面对如此多的服务账号,各个企业都会有自己 ...
- [实践] Android5.1.1源码 - 在Framework中添加自定义系统服务
前言 本文的方法要修改Android源码.但只要将系统服务写出来,也可以注入system_server进程将系统服务加载到system_server中. 步骤 1. 添加一个aidl文件,我将aidl ...
- 剑指Offer面试题:32.数字在排序数组中出现的次数
一.题目:数字在排序数组中出现的次数 题目:统计一个数字在排序数组中出现的次数.例如输入排序数组{1,2,3,3,3,3,4,5}和数字3,由于3在这个数组中出现了4次,因此输出4. 二.解题思路 2 ...
- 如何让你的JavaScript代码更加语义化
语义化这个词在 HTML 中用的比较多,即根据内容的结构化选择合适的标签.其作用不容小觑: 赋予标签含义,让代码结构更加清晰,虽然我们可以在标签上添加 class 来标识,但这种通过属性来表示本体的形 ...