c++笔记-类类型拷贝控制

  1. 一个类通过定义五种特殊的成员函数来控制对象的拷贝,移动,赋值和销毁操作,分别是拷贝构造函数、拷贝赋值构造函数、移动构造函数、移动赋值运算符和析构函数。
拷贝和移动构造函数定义了当同类型的另一个对象初始化本对象时,做什么操作
拷贝和移动赋值运算符定义了一个对象赋值给同类型的另一个对象时,做什么操作
析构函数定义了对象销毁时,做什么操作
  1. 拷贝构造函数:如果一个构造函数的第一个参数时自身类型的引用,且任何额外的参数都有默认值,则此构造函数未拷贝构造函数。
class Foo
{
public:
    Foo();  //默认构造函数
    Foo(const Foo&);    //拷贝构造函数
};
要求:
    拷贝构造函数的第一个参数必须是一个引用类型。
    虽然我们可以定义一个接受非const引用的拷贝构造函数,但参数几乎总是一个const的引用。
如果我们没有为一个类定义拷贝构造函数,编译器会为我们定义一个。
  1. 重载运算符,本质上是函数,其名字由operator关键字,后接表示要定义的运算符的符号组成。类似任何其他函数,运算符函数也有一个返回类型和参数列表
class Foo
{
public:
    Foo& operator=(const Foo&);//赋值运算符
    //...
}
赋值运算符通常应该返回一个指向其左侧运算对象的引用。
Foo& Foo::operator=(const Foo &ths)
{
    //...
    return *this;   //返回一个此对象的引用
}
  1. 析构函数,与构造函数相反的操作。构造函数初始化对象的非static数据成员,还可能做一些其他工作;析构函数释放对象使用的资源,并销毁对象的非static数据成员。析构函数也是类的一个成员函数,由波浪号接类名构成,没有返回值,也不接收参数。
隐式销毁一个内置指针类型的成员不会delete它所指的对象,应该手动释放(块作用域中的指针指向的对象)
{
    //新作用域
    //p和p2指向动态分配的对象
    Sales_data *p = new Sales_data;
    auto p2 = make_shared<Sales_data>();    //p2是一个shared_ptr
    Sales_data item(*p);
    vector<Sales_data> vec;
    vec.push_back(*p2);
    delete p;   //这一步对p指向的对象手动进行析构函数
}
//vec是局部变量,p2是智能指针,因此离开作用域后会自己释放掉。
  1. 当一个类未定义自己的析构函数时,编译器会为它定义一个合成析构函数。
class Foo
{
public:
    ~Foo(); //析构函数
};
class Sales_data
{
public:
    ~Sales_data() { }   //Sales_data的合成析构函数
};
  1. 当一个类需要析构函数时,我们几乎可以肯定它也需要一个拷贝构造函数和一个拷贝赋值运算符。
class HasPtr
{
public:
    HasPtr(const std::string &s = std::string()):ps(new std::string()), i(0) { }
private:
    std::string *ps;
    int i;
}
这个类在构造函数中动态分配内存。合成析构函数不会delete一个指针数据成员。
因此,此类需要定义一个析构函数来释放构造函数分配的内存。
class HasPtr
{
public:
    HasPtr(const std::string &s = std::string()):ps(new std::string()), i(0) { }
    ~HasPtr() { delete ps; }    //但是单单如此会产生错误
private:
    std::string *ps;
    int i;
}
如果一个类需要自定义析构函数,几乎可以肯定需要自定义拷贝赋值运算和拷贝构造函数
需要拷贝操作的类也需要赋值操作,反之亦然
  1. 我们只能对具有合成版本的成员函数使用=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;
  1. 新标准下,我们可以通过定义未删除的函数来阻止拷贝。删除的函数,即我们虽然声明了他们,但是不能以任何方式使用他们。在函数的参数列表后加=delete来定义
struct NoCopy
{
    NoCopy() = default; //使用默认的构造函数
    NoCopy(const NoCopy&) = delete; //阻止拷贝
    NoCopy &operator=(cosnt NoCopy&) = delete;  //阻止赋值
    ~NoCopy() = default;    //使用合成的析构函数
}
与=default不同的时,定义=delete声明必须出现在函数第一次声明时,而不能再在外部进行声明。
#注意#
    我们不能删除析构函数,否则就无法销毁此类型的对象。
  1. explicit防止隐式转换,explicit关键字只能用于类内部的构造函数声明上,而不能用在类外部的函数定义上。
class things
{
    public:
        things(const std::string&name =""):
              m_name(name),height(0),weight(10){}
        int CompareTo(const things & other);
        std::string m_name;
        int height;
        int weight;
};
在使用该类时,这里things的构造函数可以只用一个实参完成初始化。所以可以进行一个隐式转换,像下面这样:
things a;
................//在这里被初始化并使用。
std::string nm ="book_1";
//由于可以隐式转换,所以可以下面这样使用
int result = a.CompareTo(nm);
#注意#
这段程序使用一个string类型对象作为实参传给things的CompareTo函数。
这个函数本来是需要一个tings对象作为实参。现在编译器使用string nm来构造并初始化一个things对象,
新生成的临时的things对象被传递给CompareTo函数,并在离开这段函数后被析构。

对于声明为explicit的构造函数
class things
{
    public:
        explicit things(const std::string&name =""):
              m_name(name),height(0),weight(0){}
        int CompareTo(const things & other);
        std::string m_name;
        int height;
        int weight;
};

在使用时不能再传入string来隐式构造,必须显示构造
std::string nm = "book_2";
int result = a.CompareTo(things(nm));   //必须显示构造

这种行为的正确与否取决于业务需要。假如你只是想测试一下a的重量与10的大小之比,这么做也许是方便的。但是假如在CompareTo函数中还涉及到了要除以初始化为0的height属性,那么这么做可能就是错误的。需要在构造tings之后更改height属性不为0。
google的c++规范中提到explicit的优点是可以避免不合时宜的类型变换,缺点无。所以google约定所有单参数的构造函数都必须是显示的,只有极少数情况下拷贝构造函数可以不声明称explicit。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值