zoukankan      html  css  js  c++  java
  • 从引用传递到设计模式 (上)

    1  值传递

      值传递是 拷贝实参的值 传给形参,常用于“小对象” (small objects)

    int fact(int val)   // factorial of val 
    {
        int ret = 1; 
       
        while (val > 1)  // assign ret * val to ret and decrement val
            ret *= val--; 
        
        return ret;
    }

      调用下面函数,便是值传递:

    cout << "5! is " << fact(5) << endl;

      小对象一般为:内置类型(built-in types),STL迭代器,函数对象类型(function object types)

      只包含一对数据(x,y) 的 Point 类,也视为小对象

    void Point::operator+=(Point delta); // pass-by-value

    2  引用传递

      引用传递不涉及拷贝,传递给形参的是实参变量的引用,其有两个优点:更高效和防切断,常用来传递“大数值” (large values) 

    2.1  更高效

      基类 Person,派生类 Student

    class Person{
    private: std::string name; std::string address; }; class Student: public Person{
    private: std::string schoolName; std::string schoolAddress; };

      现有一个验证学生身份的函数,形参为值传递,则拷贝实参给形参的代价是:

      调用 Person 构造函数一次,基类内 string 型数据成员的构造函数两次;Student 构造函数一次,派生类内 string 型数据成员两次;最后还会调用相应的析构函数六次,共计十二次调用,自然效率低下。

      而引用传递,并不涉及拷贝操作,故显著的提高了效率。

    bool validateStudent(Student s);    // pass-by-value 
    
    bool validateStudent(const Student& s);  // pass-by-reference-to-const 

    2.2  防切断

      下面的例子中,派生类 WindowWithScrollBars 中,重写了基类 Window 的虚函数 display

    class Window {
    public: 
        std::string name() const;        // return name of window
        virtual void display() const;    // draw window and contents
    };
    
    class WindowWithScrollBars : public Window {
    public:
        virtual void display() const;
    };

      在 printNameAndDisplay 函数中,调用了 dispaly 函数,而形参若采用值传递方式,则会发生“切断” (slicing),即 wwsb 调用的是 Window::display()

      因为在 printNameAndDisplay 中,并不修改传递进来的参数,假如采用 pass-by-const-reference 的形式,则会避免“切断”的发生

    void printNameAndDisplay(Window w)
    {
        std::cout << w.name();
        w.display();
    }
    
    // WindowWithScrollBars object will be sliced off
    WindowWithScrollBars  wwsb;
    printNameAndDisplay(wwsb);

    3  动态绑定

      上面"切断"的例子,实际上涉及的是 C++ 的动态绑定机制 (dynamic binding), 而动态绑定的一个关键就是引用传递,看下面例子:

    class Quote {
    public:
        std::string isbn() const;
        virtual double net_price(std::size_t n) const;
    };
    
    class Bulk_quote : public Quote { 
    public:
        double net_price(std::size_t) const override;
    };

      在 cal_total 函数中,需要调用 net_price,采用 pass-by-const-reference 形式

    double cal_total(const Quote &item, size_t n)  // calculate the price
    {
        double ret = item.net_price(n);
        return ret;
    }

      调用 Quote::net_price 还是 Bulk_quote::net_price, 取决于传递进来的参数

    // basic is type Quote; bulk is type Bulk_quote
    cal_total(basic, 20); //  Quote::net_price
    cal_total(bulk, 20);  //  Bulk_quote::net_price

       C++ 的动态绑定也叫“迟邦定”,它使程序直到运行时,才基于引用或指针绑定的对象类型,来选择调用哪个虚函数

    4  设计模式 

        前面说到,动态绑定的一个关键是引用传递。它还有另一个关键 — 虚函数,例 2.2 中的 display 为虚函数,例 3 中的 net_price 同样也是虚函数。

     4.1  模板方法

       有一种编程惯例叫做 NVI (non-virtural interface) — 非虚拟接口: 将所有的公有函数 (public) 声明为非虚拟的,也即虚函数声明为私有或保护 (private or protected)

       该 NVI 惯例的实现,是通过一种设计模式 —— 模板方法 (template method) 来完成的

     

     1)  AbstractClass: TemplateMethod 为非虚成员函数 (public),函数体内调用 PrimitiveOperation1 和 PrimitiveOperation2 两个虚函数(protected)

     2)  ConcreteClass: 继承自 AbstractClass, 重写了两个虚函数 PrimitiveOperation1 和 PrimitiveOperation2

    4.2  代码实现

      按该模式则例 3 中 Quote 里,可将 cal_total 声明为公有非虚成员函数,net_price 则声明为保护型,Bulk_quote 公有继承自 Quote,且重写虚函数 net_price

      但在实际中,只有当 cal_total 内至少包含两个类似 net_price 的操作函数 (比如先调用 net_price 再 print_price),才有使用设计模式的必要

      下面是模板方法模式的简单示例:

    class AbstractClass {
    public:
        void TemplateMethod();
    protected:
        virtual void PrimitiveOperation1() = 0;
        virtual void PrimitiveOperation2() = 0;
    };
    
    class ConcreteClass : public AbstractClass {
    protected:
        void PrimitiveOperation1() override;
        void PrimitiveOperation2() override;
    };
    
    void AbstractClass::TemplateMethod()
    {
        PrimitiveOperation1();
        PrimitiveOperation2();
    }

       由上面的例子可以看到,模板方法是关于基类如何调用派生类内操作函数的,是一种反向控制结构,常用于代码复用。

      这种反向结构也体现了一个设计原则,即好莱坞原则 — “不要给我们打电话,我们会打给你”

    小结:

    1) use pass-by-value for small objects;use pass-by-const-reference to pass large values that you don’t need to modify

    2) dynamic binding happens when a virtual function is called through a reference (or a pointer) to a base class

    3) Tempalte method pattern - define the skeleton of an algorithm in an operation, deferring some steps to subclasses.

    参考资料:

     <C++ Programming Language_4th> ch 12.2.1

     <Effective C++_3rd> item 20

     <C++ Primer_5th> ch 6.2,  ch 15.1

     <Design Patterns> template method

  • 相关阅读:
    git切换仓库 小记
    修改prometheus默认端口,修改grafana默认端口
    Redisson报错
    Windows IDEA Community 报错
    Debouncer防抖代码
    IDEA通用配置
    Jackson通用工具类
    SpringBoot接入两套kafka集群
    博客园什么时候有的高低贵贱制度???
    致已经逝去的2020和已经到来的2021
  • 原文地址:https://www.cnblogs.com/xinxue/p/5531966.html
Copyright © 2011-2022 走看看