拷贝控制
拷贝赋值和销毁
拷贝构造函数
- 一个构造函数第一个参数是自身类型的引用,且任何额外参数都有默认值
class Foo{
public:
Foo();
Foo(const Foo&);
}
- 合成的拷贝构造函数用来阻止我们拷贝该类类型的对象,会将其参数的成员逐个拷贝到正在创建的对象中。数组类型,合成构造函数会逐元素地拷贝一个数组类型的成员。
class Sales_data{
public:
Sales_data(const Sales_data&);
private:
std::string bookNo;
int units_sold = 0;
double revenue = 0.0;
};
Sales_data:Sales_data(const Sales_data& orig):bookNo(orig.bookNo),units_sold(orig.units_sold),revenue(orig.revenue){}
- 拷贝初始化何时发生,以及拷贝初始化是依靠拷贝构造函数或移动构造函数来完成的。何时发生:=,实参传递给一个非引用类型的形参,返回类型为非引用的函数返回一个对象,用花括号列表初始化一个数组中的元素或一个聚合类中的成员。初始化标准库容器调用其insert和push成员,容器会对其元素进行拷贝初始化,而用emplace成员创建的元素都进行直接初始化。
- 参数和返回值:拷贝构造函数被用来初始化非引用类类型参数,所以拷贝构造函数的参数必须是引用类型,否则就会无限循环调用。
- 拷贝初始化的限制:使用的初始化要求通过一个explicit的构造函数来进行类型转换,那么使用拷贝初始化还是直接初始化就不是无关紧要的了
vector<int> v1(10);
vector<int> v2 = 10;
void f(vector<int>);
f(10);
f(vector<int>(10));
string null_book = "";
string null_book("");
拷贝赋值运算符
Sales_data trans,accum;
trans = accum;
class Foo{
public:
Foo& operator=(const Foo&);
};
合成拷贝赋值运算符
Sales_data& Sales_data::operator=(const Sales_data &rhs){
bookNo = rhs.bookNo;
units_sold = rhs.units_sold;
revenue = rhs.revenue;
return *this;
}
析构函数
class Foo{
public:
~Foo();
};
三/五法则
- 需要析构函数的类也需要拷贝和赋值操作。
- 需要拷贝操作的类也需要赋值操作,反之亦然。
使用=default
- 可以通过将拷贝控制成员定义为=default来显式地要求编译器生成合成的版本。
class Sales_data{
public:
Sales_data() = default;
Sales_data(const Sales_data&) = default;
Sales_data& operator=(const Sales_data&);
~Sales_data() = default;
};
Sales_data& Sales_data::operator=(const Sales_data&) = default;
- 类内使用default,合成的函数将隐式声明为内联的,只对类外定义使用=default,则不是内联的。
阻止拷贝
- 大多数类应该定义默认构造函数、拷贝构造函数和拷贝赋值运算符,无论是隐式地还是显式地。
- 定义删除的函数阻止拷贝,赋值
- 虽然声明了它们,但是不能以任何方式使用它们。
- 如果一个类有数据成员不能默认构造、拷贝、复制或者销毁,则对应的成员函数将被定义为删除的。
- 老版本使用private声明来阻止拷贝。
struct NoCopy{
NoCopy() = default;
NoCopy(const NoCopy&) = delete;
NoCopy& operator=(const NoCopy&) = delete;
~NoCopy() = default;
}
struct NoDtor{
NoDtor() = default;
~NoDtor() = default;
};
NoDtor nd;
NoDtor *p = new NoDtor();
delete p;
拷贝控制和资源管理
- 类的行为可以像一个值,也可以像一个指针。行为像值:对象有自己的状态,副本和原对象是完全独立的。行为像指针:共享状态,拷贝一个这种类的对象时,副本和原对象使用相同的底层数据。
- 行为像值的类
class HasPtr{
public:
HasPtr(const string& s = string()):ps(new string(s)),i(0){}
HasPtr(const HasPtr& p):ps(new string(*p.ps)),i(p.i){}
HasPtr& operator=(const HasPtr&);
~HasPtr() {delete ps;}
private:
string *ps;
int i;
};
HasPtr& HasPtr::operator=(const HasPtr &rhs){
auto newp = new string(*rhs.ps);
delete ps;
ps = newp;
i = rhs.i;
return *this;
}
class HasPtr{
public:
HasPtr(const string& s = string()):ps(new string(s)),i(0),use(new size_t(1)){}
HasPtr(const HasPtr& p):ps(p.ps),i(p.i),use(p.use){++*use;}
HasPtr& operator=(const HasPtr&);
~HasPtr();
}
HasPtr::~HasPtr(){
if(--*use == 0){
delete ps;
delete use;
}
}
HasPtr& HasPtr::operator=(const HasPtr& rhs){
++*rhs.use;
if(--*use == 0){
delete ps;
delete use;
}
ps = rhs.ps;
i = rhs.i;
use = rhs.use;
return *this;
}
交换操作
class HasPtr{
friend void swap(HasPtr&,HasPtr&);
};
inline void swap(HasPtr &lhs,HasPtr *rhs){
using std::swap;
swap(lhs.ps,rhs.ps);
swap(lhs.i,rhs.i);
}
void swap(Foo &lhs,Foo &rhs){
std::swap(lhs.h,rhs.h);
}
void swap(Foo &lhs,Foo &rhs){
using std::swap;
swap(lhs.h,rhs.h);
}
HasPtr& HasPtr::operator=(HasPtr rhs){
swap(*this,rhs);
return *this;
}
拷贝控制示例
class Message{
friend class Folder;
public:
explicit Message(const string& str = ""):contents(str){}
Message(const Message&);
Message& operator=(const Message&);
~Message();
void save(Folder&);
void remove(Folder&);
private:
string contents;
set<Floder*> folders;
void add_to_Folders(const Message&);
void remove_from_Folders();
};
void Message::save(Folder& f){
folders.insert(&f);
f.addMsg(this);
}
void Message::remove(Folder& f){
folders.erase(&f);
f.remMsg(this);
}
void Message::add_to_Folders(const Message& m){
for(auto f : m.folders)
f->addMsg(this);
}
Message:Message(const Message& m):contents(m.contents),folders(m.folders){
add_to_Folders(m);
}
void Message::remove_from_Folders(){
for(auto f:folders)
f->remMsg(this);
}
Message::~Message(){
remove_from_Folders();
}
Message& Message::operator=(const Message& rhs){
remove_from_Folders();
contents = rhs.contents;
folders = rhs.folders;
add_to_Folders(rhs);
return *this;
}
void swap(Message& lhs,Message& rhs){
using std::swap;
for(auto f:lhs.folders)
f->remMsg(&lhs);
for(auto f:rhs.folders)
f->remMsg(&rhs);
swap(lhs.folders,rhs.folders);
swap(lhs.contents,rhs.contents);
for(auto f:lhs.folders)
f->addMsg(&lhs);
for(auto f:rhs.folders)
f->addMsg(&rhs);
}
动态内存管理类
class StrVec{
public:
StrVec():elements(nullptr),first_free(nullptr),cap(nullptr){}
StrVec(const StrVec&);
StrVec& operator=(const StrVec&);
~StrVec();
void push_back(const string&);
size_t size() const {return first_free - elements;}
size_t capacity() const {return cap - elements;}
string *begin() const{return elements;}
string *end() const {return first_free;}
private:
static std::allocator<string> alloc;
void chk_n_alloc(){if(size()==capacity()) reallocate();}
std::pair<string*,string*> alloc_n_copy(const string*,const string*);
void free();
void reallocate();
string *elements;
string *first_free;
string *cap;
};
void StrVec::push_back(const string& s){
chk_n_alloc();
alloc.construct(first_free++,s);
}
pair<string*,string*> StrVec::alloc_n_copy(const string *b,const string *e){
auto data = alloc.allocate(e - b);
return {data,uninitialized_copy(b,e,data)};
}
void StrVec::free(){
if(elements){
for(auto p = first_free;p!=elements;)
alloc.destroy(--p);
alloc.deallocate(elements,cap-elements);
}
}
StrVec::StrVec(const StrVec& s){
auto newdata = alloc_n_copy(s.begin(),s.end());
elements = newdata.first;
first_free = cap = newdata.second;
}
StrVec::~StrVec(){free();}
StrVec& StrVec::operator=(const StrVec &rhs){
auto data = alloc_n_copy(rhs.begin(),rhs.end());
free();
elements = data.first;
first_free = cap = data.second;
return *this;
}
void StrVec::reallocate(){
auto newcapacity = size() ? 2 * size() : 1;
auto newdata = alloc.allocate(newcapacity);
auto dest = newdata;
auto elem = elements;
for(size_t i = 0;i != size();++i)
alloc.construct(dest++,std::move(*elem++));
free();
elements = newdata;
first_free = dest;
cap = elements + newcapacity;
}
对象移动
- 很多拷贝操作后,原对象会被销毁,因此引入移动操作可以大幅度提升性能。
- 在新标准中,我们可以用容器保存不可拷贝的类型,只要它们可以被移动即可。
- 标准库容器、string和shared_ptr类既可以支持移动也支持拷贝。IO类和unique_ptr类可以移动但不能拷贝。
右值引用
- 新标准引入右值引用以支持移动操作。通过&&获得右值引用。
- 只能绑定到一个将要销毁的对象,该对象没有其他用户。常规引用可以称之为左值引用。左值持久,右值短暂。
- 右值要么是字面值常量,要么是在表达式求值过程中创建的临时对象。使用右值引用的代码可以自由地接管所引用的对象的资源
int i =42;
int &r = i;
int &&rr = i;
int &r2 = i * 42
const int &r3 = i*42;
int &&rr2 = i*42;
- 变量是左值,右值引用不能绑定到一个右值引用类型的变量上。变量是持久的,直至离开作用域才被销毁。
int &&rr1 = 42;
int &&rr2 = rr1;
标准库move函数
- 可显式将一个左值转换为对应的右值引用类型,move告诉编译器,我们有一个左值,但我希望像右值一样处理它。
- 调用move意味着:除了对rr1赋值或者销毁它外,我们将不再使用它
int &&rr3 = std::move(rr1);
移动构造函数和移动赋值函数
StrVec::Strvec(StrVec &&s) noexcept:elements(s.elements),first_free(s.first_free),cap(s.cap){
s.elements = s.first_free = s.cap = nullptr;
}