5

5,第1张

零、复习

接口继承 vs 实现继承

接口继承:如子类重写父类的纯虚函数---每个子类都继承了这样一个“接口”,每个子类都必须给这个函数一个独有的定义,否则这个子类将还是一个抽象类

实现继承:基类定义了一个有具体行为的虚函数,而非纯虚函数

这个时候子类就有选择:它既可以选择直接继承这个虚函数,直接把基类的实现继承下来 直接用他

也可以自己override重写


一、Copy Control

Copy and Swap

首先别忘了inline是干嘛的:C++ 中的 inline 用法 | 菜鸟教程

 

 上面是algorithm库中swap函数的实现

这种交换方式的三个等号分别是三次拷贝 *** 作,效率低下

我们打算这样做:

 

 仔细看上面,swap变成了Vector类的成员函数,然后对要交换的成员函数/指针,我们再调用std::swap来交换即可(std::swap用来交换数字,指针  效率还是很高的)

其中,noexcept关键字表示“不抛出异常”

 利用上面在类中写的的swap,我们可以有一种安全简洁的拷贝复制运算符的实现方式:

 这种方式让 拷贝赋值运算符 依赖于 拷贝构造函数


Prevent Copying 禁止拷贝

C++11后,用 =delete 即可达到目的

这里考虑另外一种方法:涉及到 空基类优化  Empty Base Optimization (EBO)

 注意基类Uncopyable 没有说访问权限是什么,默认private

关键是:private作用域下只声明不定义

子类继承之后,编译器无法自动生成拷贝函数

但是上面方法有个小问题:Uncopyable类型的指针或者引用 可以 与 任何 Uncopyable子类的对象产生绑定关系!别忘了 Uncopyable不同子类之间的对象是没有什么关系的!我们要避免这种情况!

问题关键就是:继承关系现在是公开的,因为是public继承

所以我们只需要用private继承即可

 

私有继承的理解:继承关系是个秘密

 



二、Resource-managing Classes 资源管理类 Surrogate 代理

 

 

 正确的方法如下:

 注意,上面重写的时候,返回值分别改成Rectangle*, Circle* 也是正确的重写方式

因为别忘了重写虽然要求identical,但是有一点例外:返回类型既可以是identical,也可以是covariant with(协变)----忘了?看上次gkxx recitation9!

#include 
#define PI 3.141592653589793
using namespace std;

class Shape_base{
public:
    virtual double perimeter() const = 0;
    virtual double area() const = 0;
    virtual ~Shape_base() = default;
    virtual Shape_base* clone() const = 0;
};

class Rectangle : public Shape_base{
    double height, width;
public:
    Rectangle(double h, double w):height(h),width(w) {}
    virtual double perimeter() const override{
        return 2 * (height + width);
    }
    virtual double area() const override{
        return height * width;
    }
    virtual Rectangle* clone() const override{
        return new Rectangle{height,width};//这里的花括号说是一种modern风格,用圆括号也行
    }
};

class Circle:public Shape_base{
    double radius;
public:
    Circle(double r):radius(r){}
    virtual double perimeter() const override{
        return 2 * PI * radius;
    }
    virtual double area() const override{
        return PI * radius * radius;
    }
    virtual Circle* clone() const override{
        return new Circle{radius};
    }
};

int main()
{


    system("pause");
    return 0;
}
Surrogate 代理定义

 

然后我们考虑Shape对象之间的拷贝 *** 作改如何实现

 

 

 上面Shape类中的stretech函数并没有修改Shape类的成员

注意,Shape类中的成员是Shape_base* bp,即指针

只有这个指针被修改 才叫做修改

而stretch函数仅仅是调用了这个指针所指对象的stretch函数,所以编译器中我们加上const是没有问题的

但是从逻辑上讲,或者说从最终的效果来看,我们最后调用的,这个指针所指对象的stretch函数,确确实实修改了所指对象的成员参数(使长方形或者圆的数据增大m倍)

所以从逻辑上讲,我们这里不用写const

下面是代理类的使用

 我们开了一个装有Shape类型的数组

我们不需要人为再new,delete,并且还有动态绑定的特性,非常方便

总之,代理既有内存管理的功能,也有动态绑定的特性

#include 
#define PI 3.141592653589793
using namespace std;

class Shape_base{
    friend class Shape;

    virtual double perimeter() const = 0;
    virtual double area() const = 0;
    virtual Shape_base* clone() const = 0;
    virtual void stretch(double) = 0;

protected:
    virtual ~Shape_base() = default;
};

class Shape{
    friend Shape make_rectangle(double, double);
    friend Shape make_circle(double);

    Shape_base *bp;
public:
    Shape():bp(nullptr){}
    double perimeter() const {
        return bp->perimeter();
    }
    double area() const {
        return bp->area();
    }
    bool is_null() const{
        return !bp;
    }
    void stretch(double m){
        bp->stretch(m);
    }
    
    Shape(const Shape &other)
        :bp(other.bp? other.bp->clone():nullptr){}
    Shape& operator=(const Shape &other){
        auto copy = other.bp ? other.bp->clone() : nullptr;
        delete bp;//不能一上来直接delete bp是为了考虑other就是自己的情况
        bp = copy;
        return *this;
    }
    ~Shape() {
        delete bp;
    }
private:
    Shape(Shape_base* p):bp(p){}
};

class Rectangle : public Shape_base{
    friend Shape make_rectangle(double, double);

    double height, width;

    Rectangle(double h, double w):height(h),width(w) {}
    virtual double perimeter() const override{
        return 2 * (height + width);
    }
    virtual double area() const override{
        return height * width;
    }
    virtual Rectangle* clone() const override{
        return new Rectangle{height,width};//这里的花括号说是一种modern风格,用圆括号也行
    }
    virtual void stretch(double m) override{
        height *= m;
        width *= m;
    }
};

inline Shape make_rectangle(double h,double w){
    return new Rectangle{h, w};
}

class Circle:public Shape_base{
    friend Shape make_circle(double);

    double radius;

    Circle(double r):radius(r){}
    virtual double perimeter() const override{
        return 2 * PI * radius;
    }
    virtual double area() const override{
        return PI * radius * radius;
    }
    virtual Circle* clone() const override{
        return new Circle{radius};
    }
    virtual void stretch(double m) override{
        radius *= m;
    }
};

inline Shape make_circle(double r){
    return new Circle(r);
}

int main()
{


    system("pause");
    return 0;
}

新的术语:

值语义 Value Semantics

引用语义 Refenrence Semantics

 我们刚刚实现的Shape就是值语义

 



Reference-counting Handles 引用计数句柄

handle 句柄

 

 

 

 

 我们的懒惰思想是:只有当要进行像stretch函数这样,要修改成员的 *** 作时

我们再来根据值语义来拷贝

其他时候引用语义就够了

 

 

最后几句话大概就是介绍了一下:这节课讲的资源管理类的部分主要来自于Ruminations on C++的第5-7章;第8章是用reference-counting handle实现第五次作业的第三题(也是这道题的idea来源);第9章和第10章实现了一个更有意思的例子。另外,Effective C++的Item 13-17讲了资源管理,其中Item 15和17说了一些我没有提到的注意事项。此外自C++11起标准库中以“智能指针”的形式实现了这些资源管理类,在C++ Primer第12章第1节中解释了这些智能指针的基本用法,在Effective Modern C++ Item 18-22中具体讲解了在什么场合下该如何使用智能指针。

去写作业喽~

 

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/867094.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-12
下一篇2022-05-12

发表评论

登录后才能评论

评论列表(0条)

    保存