C++ study notes (15) - object-oriented programming

C++ study notes (15) - object-oriented programming

OOP: Overview

Object-oriented programming is based on three fundamental concepts: data abstraction, inheritance, and dynamic binding.

  • Data abstraction: separate the interface and implementation of the class, details can be found in C++ study notes (7) - class review
  • Inheritance: You can define similar types and model their similar relationships. Inheritance builds a hierarchical relationship. The root of the hierarchy is the base class, and other classes are directly or indirectly inherited from the base class, which is called a derived class.
  • Dynamic binding: Ignore the distinctions of similar types to a certain extent, and use their objects in a uniform way. That is, the running version of the function is determined by the actual parameters, that is, the version of the function is selected at runtime, which is also called run-time binding (run-time binding)
  • Virtual function (virtual function) : some functions that the base class wants the derived class to redefine will be declared as a virtual function by the base class

Define base and derived classes

define base class

class Quote{
public:
    Quote() = default;
    Quote(const std::string &book, double sales_price): bookNo(book), price(sales_price){}
    std::string isbn() const { return bookNo; }
    // 返回给定数量的销售总额,派生类负责改写并使用不同的折扣计算算法
    virtual double net_price(std::size_t n) const { return n * price; }
    virtual ~Quote() = default;  // 虚析构函数,进行动态绑定
private:
    std::string bookNo;  // 书籍的ISBN 编号
protected:
    double price = 0.0;  // 代表普通状态下不打折的价格
};
  • Base classes should generally define a virtual destructor, even if the function does nothing
  • The base class declares a member function virtualso that the function performs dynamic binding
  • The keyword virtualcan only appear before the declaration statement inside the class, and cannot be used in the function definition outside the class
  • Any non-static function outside the constructor can be virtual
  • If the base class declares a function virtual, the function is also implicitly virtual in the derived class
  • Resolution of virtual functions happens at runtime, not compile time
  • Protected (protected) access operator indicates that the base class member wants the derived class member to have access, but other users are prohibited from accessing

define a derived class

Derived classes must explicitly indicate from which base class they inherit by using the class derivation list

class Bulk_quote : public Quote{  // Bulk_quote 继承自 Quote
public:
    Bulk_quote() = default;
    Bulk_quote(const std::string& book, double p, std::size_t qty, double disc): 
        Quote(book, p), min_qty(qty), discount(disc) {}
    // 覆盖基类的函数版本以实现基于大量购买的折扣政策
    double net_price(std::size_t) const override
    {
        {
            if(cnt >= min_qty)
                return cnt * (1 - discount) * price;
            else
                return cnt * price;
        }
    }
private:
    std::size_t min_qty = 0;  // 使用折扣政策的最低购买量
    double discount = 0.0;  // 以小数表示的折扣额
};
  • Objects of a shared derived type can bind to a reference or pointer of the base class, i.e. Quotea reference or pointer of an Bulk_quoteobject can be used

  • If the derived class does not override the virtual function of the base class, it directly inherits the base class version

  • overrideThe keyword indicates that the derived class overrides the virtual function of the base class

  • An object of a derived class can be used as an object of the base class, or a pointer or reference of the base class can be bound to the base class part of the object of the derived class

  • The following code is a derived-to-base type conversion, which is implicitly performed by the compiler

    Quote item;  // 基类对象
    Bulk_quote bulk;  // 派生类对象
    Quote *p = &item;  // p 指向 Quote 对象
    p = &bulk;  // p 指向 buld 的 Quote 部分
    Quote &r = bulk;  // r 绑定到 bulk 的 Quote 部分
  • First initialize the base class part, pass the parameters to Quotethe constructor of the constructor, and then initialize the derived class members sequentially in the order of declaration

  • Each class is responsible for defining its own interface. To interact with an object of a class, the interface of the class must be used, even if the object is the base class of the derived class, so the derived class object cannot directly initialize the base class members

  • Static members defined by base classes have only unique definitions in the entire inheritance system

  • The premise of a class being used as a base class is that it has been defined, not just declared

  • C++11 defines keywords finalto prevent inheritanceclass NoDerived final { /* */ };

Type Conversion and Inheritance

  • Static type : known at compile time, it is the type of the variable declaration or the type generated by the expression
  • Dynamic type : The type of the object in memory represented by the variable or expression, known only at runtime
  • Base classes cannot be implicitly converted to derived classes, but static_castconversions can be used, as long as it is safe to do so
  • The automatic type conversion from the derived class to the base class is only valid for pointer or reference types, and the conversion between the derived class object and the base class object occurs through the copy/move constructor and assignment operator

virtual function

A virtual function must be defined

  • When a virtual function is called with a pointer or reference, the compiler knows the runtime to determine what type of object the reference or pointer is bound to. When a virtual function is called through an expression with a normal type (not a reference, not a pointer), it can be determined at compile time

    Quote base;
    Quote *p = &item;
    p.net_price(10);  // 调用 Quote::net_price
    Bulk_quote derived;
    p = &derived;
    p.net_price(20);  // 调用 Bulk_quote::net_price
  • Dynamic binding only happens when calling a virtual function through a pointer or reference

    base = derived;  // 把 derived 的 Quote 部分拷贝给 base
    base.net_price(20);  // 调用 Quote::net_price
  • The virtual function in the base class is also implicitly a virtual function in the derived class. If the function of the derived class overrides the inherited virtual function, its parameter type must strictly match the overridden base class function, but the return The type just needs to match the base class function

  • overrideThe keyword indicates that the derived class overrides the virtual function of the base class. finalAvailable for functions, indicating that the function is not overridable. These two specifiers appear after the parameter list (including any constor reference modifiers) and the trailing return type

  • If the virtual function uses default arguments, the default arguments defined in the base class and the derived class should be consistent, otherwise the default arguments defined in the base class are used

  • A dynamic binding mechanism for virtual functions can be implemented using scope operators, usually only required for member functions (or friends)

abstract base class

  • Pure virtual function : no need to define, just write it before the function body =0, it =0can only appear at the virtual function declaration statement inside the class
  • Abstract base class : A class that contains (or directly inherits without overriding) pure virtual functions. Abstract classes are responsible for defining interfaces and cannot create objects of abstract classes
class A{
public:
    virtual int func() = 0;   // 纯虚函数,故 A 为抽象类
};

Access Control and Inheritance

Each class controls its own member initialization process. Similarly, each class also controls whether its members are accessible to derived classes.

protected member

  • Like private members, protected members are not accessible to users of the class
  • Like public members, protected members are accessible to members and friends of derived classes
  • Members or friends of a derived class can only access protected members of the base class through the derived class object. A derived class does not have any access privileges to protected members of a base class object
class Base{
protected:
    int prot_mem;  // protected 成员
};
class Sneaky: public Base{
    friend void clobber(Sneaky&) { s.j = s.prot_mem = 0; }  // 能访问 Sneaky::prot_mem
    friend void clobber(Base&) { b.prot_mem = 0; }  // 不能访问 Base::prot_mem
    int j;
};

Public, Private and Protected Inheritance

The access rights of a class to its inherited members are affected by two factors: one is the access specifier of the member in the base class, and the other is the access specifier in the derived class's derived list

  • The purpose of the derived access specifier is to control the access rights of users of the derived class (including derived classes of the derived class) to members of the base class
  • Derived class cannot access privatemembers in base class, regardless of inheritance
  • Derived class users cannot access privateinherited members

Accessibility of derived class to base class conversion

  • The conversion from derived class to base class can only be used if the derived class publicly inherits the base class
  • Member functions and friends of a derived class, you can use the conversion from the derived class to the base class
  • If the derived class publicly or protected inherits the base class, the members and friends of the derived class can use the derived class to base class conversion. If private, you cannot

  • Friendships cannot be passed or inherited, and each class is responsible for controlling the access rights of its members

  • usingChange the access level of a member inherited by a derived class by using a declaration
  • By default, classderived classes defined using keywords inherit privately, and structderived classes defined using keywords inherit publicly.

class scope in inheritance

The scope of the derived class is nested within the scope of its base class. If a name cannot be resolved in the derived class scope, it will be searched in the outer base class scope.

  • Name lookup happens at compile time, i.e. static typing determines which members are visible, even though static typing may not match dynamic typing

  • Derived classes can reuse names defined in their base classes or indirect base classes, in which case the names defined in the outer scope (ie the base class) will hide the names defined in the outer scope (ie the base class)

  • Hidden members can be used by using the scope operator
  • usingThe declaration statement can add all overloaded instances of a function in the base class to the derived class scope

Constructor and copy control

virtual destructor

If the static type does not match the dynamic type, the compiler expects to point to the destructor of the dynamic type, so it is necessary to define the destructor as a virtual function in the base class to ensure that the correct version of the destructor is executed

  • deleteA base class pointer to a derived class object yields undefined behavior if the base class's destructor is not virtual
  • Base classes usually require destructors and make them virtual

Synthetic Copy Control and Inheritance

The way the base class is defined can cause the derived class members to be deleted functions

  • If the default constructor, copy constructor, copy assignment operator or destructor in the base class is a deleted function or is inaccessible, the corresponding member in the derived class will be deleted because the compiler cannot use the base class member to perform the construction, assignment, or destruction of the base class portion of the derived class object
  • If there is an inaccessible or deleted destructor in the base class, the synthetic default and copy constructors in the derived class will be deleted because the compiler cannot destroy the base class part of the derived class object
  • When using a =defaultrequest for a move operation, if the corresponding operation in the base class is delete or inaccessible, the function in the derived class will be deleted because the base class part of the derived class object is not moveable

Copy-Controlled Members of Derived Classes

  • When a derived class defines a copy or move operation, the operation is responsible for copying or moving the entire object including partial members of the base class
  • By default, the default constructor of the base class initializes the base class part of the object, if we want to copy or move the base class part, we must display the call to the base class copy or move constructor in the derived class constructor initializer list
class Base { /* ... */ }
class D: public Base{
public:
    D(const D& d): Base(d) /* D 成员初始值 */ { /* ... */ }  // 拷贝基类成员
    D(D&& d): Base(std::move(d)) /* D 成员初始值 */ { /* ... */ }  // 移动基类成员
};
  • Unlike constructors, which are assignment operators, derived class destructors are only responsible for destroying resources allocated by the derived class itself
  • If a constructor or imaginary function calls a virtual function, the version of the virtual function corresponding to the type to which the constructor or destructor belongs should be executed

Inherited constructor

  • Classes cannot inherit default, copy and move constructors. If the derived class does not define these constructors directly, the compiler will synthesize them
  • Normally, the usingdeclaration statement just makes a name visible in the current scope, and when used as a constructor, the usingdeclaration statement causes the compiler to generate code, that is, for each constructor of the base class, the compiler is in the derived class Generates a constructor with identical parameter lists
  • declaration of a constructor usingdoes not change the access level of that constructor
  • When the base class constructor has default arguments, those arguments are not inherited
class Bulk_quote: public Disc_quote{
public:
    using Disc_quote::Disc_quote;  // 继承 A 的构造函数, 等价于
    /*
    若派生类有自己的数据成员,则会默认初始化
    Bulk_quote(const std::string&book, double price, std::size_t qty, double disc):
        Disc_quote(book, price, qty, disc) {}
    */
};

Containers and Inheritance

When a container is used to store objects in an integrated system, the profile storage method is usually used. Because it is not allowed to store elements of different types in the container

  • When the derived class object is assigned to the base class object, the derived class part will be cut off, so the container and the type with inheritance relationship cannot be compatible
  • You can store the base class pointer (a better choice is a smart pointer) to store the object with inheritance relationship, because the dynamic type of the object pointed to by the pointer may be the base class type or the derived type
class B{
public:
    virtual void info() { cout << str << endl; }
private:
    std::string str = "B";
};
class D: public B{
public:
    void info() override { cout << str2 << endl; }
private:
    std::string str2 = "D";
};

vector<shared_ptr<B>> vec;
vec.push_back(make_shared<B>());
vec.push_back(make_shared<D>());
vec[0]->info();  // B
vec[1]->info();  // D

vector<B> vec2;
vec2.push_back(B());
vec2.push_back(D());
vec2[0].info();  // B
vec2[1].info();  // B, 派生部分被截断

Epilogue

The combination of inheritance and dynamic binding allows us to write programs with type-specific behavior that is independent of the type

Dynamic binding in C++ only works on virtual functions and is called by pointer or reference

When constructing, copying, moving, and assigning a derived class, the base class part of it is constructed, copied, moved, and assigned first, and then the derived class part. Destructors are executed in the reverse order, with the derived class being destroyed first, followed by the destructors of the base class subobjects.

Base classes usually need to define a virtual destructor

Derived classes provide a level of protection for each base class, publicthe members of the base class are also part of the interface of the derived class, the members of the privatebase class are not accessible, protectedthe members of the base class are accessible to the derived class of the derived class, but for the derived class User inaccessible

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325702886&siteId=291194637