【Effective C++】2. 构造/析构/赋值运算

Item05 了解C++默默编写并调用了哪些函数

1. 定义空类并使用空类定义变量的时候,编译器会默认生成以下函数:

  • (无参)默认构造函数
  • 拷贝构造函数
  • 拷贝赋值函数
  • 析构函数
// 只有需要的时候才会创建
class Empty {
public:
    Empty() {}                  // 默认构造函数
    Empty(const Empty& rhs) {}  // 默认拷贝构造
    ~Empty() {}                 // 默认析构函数

    Empty& operator=(const Empty& rhs) {} // 默认拷贝赋值函数
};

2. 如果显式定义了其中的任何一个特殊成员函数,编译器将不会生成默认的对应函数;

  • 如,在该例子中声明了构造函数,然后定义一个变量,会报 no matching function 的错误;
  • 对于默认的拷贝构造的操作,
    • nobj3初始化nameValue时,会调用string的拷贝构造,参数为nobj1.nameValue;
    • nobj3初始化objectValue时,会将nobj1.objectValue每个bits拷贝到nobj3.objectValue;
  • 对于默认拷贝赋值操作,行为和拷贝构造一样
template<typename T>
class NameObject {
public:
    NameObject(const std::string& name, const T& value) : nameValue(name), objectValue(value) { }
private:
    std::string nameValue;
    T objectValue;
};

#include "item_05.h"
int main() {
    NameObject<int> nobj;
    NameObject<int> nobj1("name", 5);
    NameObject<int> nobj2("nobj2", 10);
    NameObject<int> nobj3(nobj1); // copy construct
    nobj1 = nobj2;                // copy assignment
    return 0;
}

// main.cpp:9:21: error: no matching function for call to ‘NameObject<int>::NameObject()’
//     9 |     NameObject<int> nobj;

3. 默认拷贝赋值函数只有生成代码合法的情况下才会被创建

  • 如果成员变量是引用或者是const类型,
    • 赋值过程会改变引用或者const变量的值,所以默认拷贝赋值函数不能被生成
    • 引用的值定义的时候必须被初始化,且以后不能改变指向;
  • 如果父类中的拷贝赋值是私有的,继承的子类也不会生成默认拷贝赋值函数
    • (条款12会详细介绍)
template<typename T>
class NameObject {
public:
    NameObject(std::string& name, const T& value) : nameValue(name), objectValue(value) { }
private:
    std::string& nameValue;
    const T objectValue;
};

#include "item_05.h"
int main() {
    std::string name_obj1("obj1");
    std::string name_obj2("obj2");
    NameObject<int> nobj1(name_obj1, 5);
    NameObject<int> nobj2(name_obj2, 10);
    nobj1 = nobj2;
    return 0;
}

/*
 * 错误信息
main.cpp: In function ‘int main()’:
main.cpp:14:13: error: use of deleted function ‘NameObject<int>& NameObject<int>::operator=(const NameObject<int>&)’
   14 |     nobj1 = nobj2;
      |             ^~~~~
In file included from main.cpp:3:
item_05.h:18:7: note: ‘NameObject<int>& NameObject<int>::operator=(const NameObject<int>&)’ is implicitly deleted because the default definition would be ill-formed:
   18 | class NameObject {
      |       ^~~~~~~~~~
item_05.h:18:7: error: non-static reference member ‘std::string& NameObject<int>::nameValue’, can’t use default assignment operator
item_05.h:18:7: error: non-static const member ‘const int NameObject<int>::objectValue’, can’t use default assignment operator
*/

Item06 若不想使用编译器自动生成的函数,就应该明确拒绝

场景:对于中介买的房子是独一无二的,为这样的资产提供副本毫无意义

  • 版本1
    • 将拷贝构造和拷贝赋值函数设置为私有
    • 但如果成员函数和友元函数访问,不会出现编译问题,会出现链接错误
  • 版本2
    • 将报错提前到编译期
    • 子类调用拷贝构造时会调用父类的拷贝构造,会被编译器拒绝
    • 会造成多重继承(见条款40)
  • 版本3
    • C++11新特性,解决上述问题
#ifndef __ITEM_06_H__
#define __ITEM_06_H__

#include <string>
#include <iostream>
// 版本1
// class HomeForSale {
// public:
//     HomeForSale() {}
// private:
//     HomeForSale(const HomeForSale&);
//     HomeForSale& operator=(const HomeForSale&);
// };

// 版本2
class Uncopyable {
protected:
    Uncopyable() {}
    ~Uncopyable() {}
private:
    Uncopyable(const Uncopyable&);
    Uncopyable& operator=(const Uncopyable&);
};
class HomeForSale : public Uncopyable {};

// 版本3 c++11新特性
class HomeForSale {
public:
    HomeForSale() {}
    HomeForSale(const HomeForSale&) = delete;
    HomeForSale& operator=(const HomeForSale&) = delete;
};

#endif

/*
 * 版本1 报错
main.cpp:20:22: error: ‘HomeForSale::HomeForSale(const HomeForSale&)’ is private within this context
   20 |     HomeForSale h3(h1);
      |                      ^
In file included from main.cpp:4:
item_06.h:10:5: note: declared private here
   10 |     HomeForSale(const HomeForSale&);
      |     ^~~~~~~~~~~
*/

/*
 * 版本2报错
main.cpp:20:22: error: use of deleted function ‘HomeForSale::HomeForSale(const HomeForSale&)’
   20 |     HomeForSale h3(h1);
      |                      ^
In file included from main.cpp:4:
item_06.h:22:7: note: ‘HomeForSale::HomeForSale(const HomeForSale&)’ is implicitly deleted because the default definition would be ill-formed:
   22 | class HomeForSale : public Uncopyable {};
      |       ^~~~~~~~~~~
item_06.h:22:7: error: ‘Uncopyable::Uncopyable(const Uncopyable&)’ is private within this context
item_06.h:19:5: note: declared private here
   19 |     Uncopyable(const Uncopyable&);
*/

/*
 * 版本3报错
main.cpp:20:22: error: use of deleted function ‘HomeForSale::HomeForSale(const HomeForSale&)’
   20 |     HomeForSale h3(h1);
      |                      ^
In file included from main.cpp:4:
item_06.h:27:5: note: declared here
   27 |     HomeForSale(const HomeForSale&) = delete;
*/

Item07 为多态基类声明virtual析构函数

虚函数:

  • 允许子类中的实现定制化;
  • 实现机制(写的不够详细):
    • 每个带有虚函数的类有一个vtbl(函数指针组成的数组);
    • 每个带有虚函数定义的对象,都包含一个vptr(虚表指针);
    • 对象调用虚函数,实际上取决于vptr指向的vtbl;
  • 包含了vptr意味着不在具有移植性,因为定义的对象大小会增加;

虚析构函数运作机制:

  • 最深层的子类的析构函数最先被调用,然后一次调用其父类的析构函数
  • 如果析构函数不是虚的,删除父类指针的时候会出现资源泄漏的行为
    • 父类的析构被调用,子类的析构没有被调用,子类申请的内存没有销毁
class TimeerKeeper {
public:
    TimeerKeeper() {}
    virtual ~TimeerKeeper() {}
};
class AtomicClock : public TimeerKeeper {};
class WaterClock : public TimeerKeeper {};
TimeerKeeper* creatTimeKeeper() { return new TimeerKeeper(); }
int main() {
    TimeerKeeper* ptk = creatTimeKeeper();
    delete ptk;
    return 0;
}

避免继承一些标准库的类,如string,stl中的容器等,避免出现子类未被析构的情况;

这些标准库的类,设计目的并非是为了多态;

class SpecialString : public std::string {};
std::string* ps = new SpecialString();
delete ps;

纯虚函数

  • 将析构函数声明为纯虚函数,需要提供一份定义;
  • 因为在派生类中会调用~AWOV(),如果没有定义,链接器会报未定义的错误;
class AWOV {
public:
    virtual ~AWOV() = 0;
};
AWOV::~AWOV() {}
class TestAWOV : public AWOV {};

int main() {
    TestAWOV t;
    return 0;
}

/*
 * 报错信息
/usr/bin/ld: /tmp/ccmxNHov.o: in function `TestAWOV::~TestAWOV()':
main.cpp:(.text._ZN8TestAWOVD2Ev[_ZN8TestAWOVD5Ev]+0x26): undefined reference to `AWOV::~AWOV()'
collect2: error: ld returned 1 exit status
*/

Item10 令operator=返回一个reference to *this

类在实现赋值操作符应该保持和内置类型同样的结构

  • 一致性:返回引用到*this可以使赋值操作的语法与其他操作符一致,例如成员函数调用或算术运算符。这样可以提高代码的一致性和可读性。

  • 可修改性:返回引用到*this使得赋值操作可以修改当前对象的状态。这对于某些特定的操作是非常有用的,例如自赋值检测和资源管理。

  • 减少拷贝:通过返回引用,避免了不必要的对象拷贝操作,提高了性能和效率。特别是对于大型对象或需要频繁赋值的情况下,减少了内存和时间的开销

class Widget {
public:
    Widget& operator=(const Widget& rhs) {
        // 赋值操作
        return *this;
    }
};
int x, y, z;
x = y = z = 10;
Widget w1, w2, w3;
w1 = w2 = w3;

  • 39
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杨主任o_o

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值