拷贝控制

1、拷贝构造函数的第一个参数必须是引用类型,并且通常不应该是explicit的(因为可能会用到隐式转换)。

2、编译器合成拷贝构造函数用来阻止拷贝该类类型的对象,而是会将其参数成员逐个拷贝到创建的对象中。

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 & oring):bookNo(oring.bookNo),units_sold(oring.unit_sold),revenue(oring.revenue){}

3、拷贝初始化发生情况:

  • 将一个对象作为实参传递给一个非引用类型的形参。
  • 从一个返回类型为非引用类型的函数返回一个对象。
  • 用花括号初始化一个数组中的元素或一个聚合类中的成员。

4、在函数调用过程中,具有非引用类型的参数要进行拷贝初始化。拷贝构造函数的参数必须是引用类型。

5、赋值运算符通常返回一个指向其左侧运算对象的引用。

6、合成拷贝赋值运算符通过成员类型的拷贝赋值运算符完成的。

7、析构函数首先调用函数体,按照成员初始化顺序的逆序销毁。

8、隐式销毁一个内置指针类型的成员不会delete它所指向的对象。

9、析构函数的调用:

  • 变量离开其作用域时
  • 当一个对象被销毁时
  • 容器被销毁时,元素也销毁
  • 对于动态分配的对象,当指向它的指针应用delete运算符时被销毁
  • 对于临时对象,当创建它的完整表达式结束时被销毁。

10、当指向一个对象的引用或指针离开作用域时,析构函数不会执行。

11、对于一个类来讲,拷贝构造函数、拷贝赋值运算符、析构函数(移动构造函数、移动赋值运算符)是三位一体(五位一体)的。

12、如果一个类需要自定义析构函数,几乎可以肯定它也需要自定义拷贝赋值运算符和拷贝构造函数。

13、我们可以对具有合成版本的成员函数使用=default(即,默认构造函数或拷贝控制运算符)

14、阻止拷贝

struct NoCopy
{
   NoCopy()=default;
   NoCopy(const NoCopy&) = delete;//阻止拷贝
   NoCopy &operator=(const NoCopy&)=delete;//阻止赋值
   ~NoCopy()=default;//使用合成的析构函数
}

15、可以对任何函数指定delete,但是只能对编译器可以合成的默认构造函数或拷贝控制成员使用=default;

16、不能删除析构函数。

17、当不可能拷贝、赋值或销毁类的成员时,类的合成拷贝控制成员就被定义为删除的。

18、通常管理类外资源的类必须定义拷贝控制成员。

19、行为像值的类:

  • 定义拷贝构造函数
  • 定义析构函数
  • 定义拷贝赋值运算符(组合了析构函数和拷贝构造函数的工作,左侧析构右侧构造)
class HasPtr
{
   public:
     HasPtr(const std::string &s = std::string()):ps(new std::string(s),i(0)){}
     HasPtr(const HasPtr &p): ps(new std::string(*p.ps)),i(p.i){}
     HasPtr& operatro=(const HasPtr &);
     ~HasPtr(){delete ps;}
   private:
     std::stirng *ps;
     int   i;
}
HasPtr& HasPtr::operatro=(const HasPtr & rhs)
{
   auto newp = new string(*rhs.ps);
   delete ps;
   ps = newp;
   i= rhs.i;
   return *this;
}

20、令一个类展现类似指针的行为最好是使用shared_ptr来管理类中的资源。

#include 
#include 
using namespace std;
class Message
{
	friend class Folder;
public:
	explicit Message(const std::string & str = "") :contents(str){}
	Message(const Message&);
	Message& operator =(const Message &);
	~Message();
	//-----------------从给定的Folders集合中添加删除Message-----------------------------------------
	void save(Folder&);
	void remove(Folder&);
private:
	std::string contents;
	std::setfolders;
	void add_to_Folders(const Message&);//将一个message添加到一组folders中
	void remove_form_Folders();//将一个message从所有的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.folers)
{
	add_to_Folders(m);
}

21、右值引用只能绑定到一个即将被销毁的对象(引用就是一段内容暂时不能更改的内存的指针)

22、右值要么是字面常量、要么是在表达式求值过程中创建的临时对象。

23、可以调用std::move()获得绑定到左值上的右值引用。

int && rr3 = std::move(rr1);

24、移动构造函数不分配任何新内存,由于移动操作不分配认可资源。因此,移动操作通常不会抛出任何异常。

25、在一个构造函数中noexpect出现在参数列表和初始化列表开始的冒号之间。

class StrVec
{
   public:
      StrVec(StrVec&&) noexcept;
};
StrVec::StrVec(StrVec&& s) noexcept:{}

26、移动赋值运算符与析构函数相同的工作。

StrVec & StrVec::operator=(StrVec && rhs) noexcept
{
   if(this!=&rhs)
   {
      free();
      elements=rhs.elements;
      first_free=rhs.first_free;
      cap=rhs.cap;
      rhs.elements= rhs.first_free=rhs.cap=nullptr;
   }
   return * this;
}

27、当我们编写一个移动操作时,必须确保移后源对象进入一个可析构的状态。(通过将移后源对象的指针成员置为nullptr)

移动操作后,移后源对象必须保持有效可析构的状态,用户不能对其值进行任何假设。

28、如果一个类没有移动操作,通过正常的函数匹配,类会使用对应的拷贝操作代替移动操作。

29、如果一个类定义了一个移动构造函数和/或一个移动赋值运算符,则该类的合成拷贝构造函数和拷贝赋值运算符会被定义为删除的。

30、用拷贝构造函数代替移动构造函数几乎肯定是安全的。

31、区分移动和拷贝的重载函数通常有一个版本接受一个const T&,而另外一个版本接受一个T&&。

32、如果一个成员函数有引用限定符,则具有相同参数列表的所有版本都必须有引用限定符。

28、一个函数可以同时用const和引用限定。

你可能感兴趣的:(c++,拷贝控制)