[C++] 构造函数和复制控制

关于派生类对象的创建、复制、赋值和销毁,派生类的构造函数。

1 基类构造函数和复制控制

构造函数可以为protectedprivate, 一些类将构造函数定义为定义protected,只给它的派生类使用。

2 派生类构造函数

2.1 合成的派生类默认构造函数

每个派生类的构造函数都初始化它的基类,同时初始化它自己的数据成员。
派生类合成的默认构造函数和一般的合成的构造函数不同之处在于,它不仅初始化派生类的数据成语,它也初始化基类的数据成员。

基类:

     class Item_base {
     public:
         Item_base(const std::string &book = "", double sales_price = 0.0):
                          isbn(book), price(sales_price) { }
         std::string book() const { return isbn; }
         virtual double net_price(std::size_t n) const { return n * price; }
         virtual ~Item_base() { }
     private:
         std::string isbn;
     protected:
         double price;
     };

派生类:

     class Bulk_item : public Item_base {
     public:
         double net_price(std::size_t) const;
     private:
         std::size_t min_qty;
         double discount; 
      };

对于Bulk_item类, 合成的默认构造函数将像下面这样执行:
1. 调用Item_base的默认构造函数,此默认构造函数将初始化isbn成员为空字符串,以及price成员为0
2. 使用一般变量初始化规则初始化Bulk_item的成员,也就是说,成员qtydiscount不会被初始化。

2.2 定义默认构造函数

因为Bulk_item有内建类型的成员,因此必须自定义默认构造函数,自定义默认构造函数如下:

     class Bulk_item : public Item_base {
     public:
         Bulk_item(): min_qty(0), discount(0.0) { }
     };

此默认构造函数也会隐式调用Item_base的默认构造函数初始化它的基类部分。

运行这个构造函数的效果就是,首先,item_base部分被item_base的默认构造函数初始化,即isbn设为空字符串,price被设为0, 当item_base 的默认构造函数执行结束,Bulk_item的成员被初始化, 构造函数的函数体(此处为空)被执行。

2.3 传递参数给基类构造函数

派生类的构造函数一般只会初始化派生类的成员,基类的成员初始化用调用基类的构造函数间接实现:

     class Bulk_item : public Item_base {
     public:
         Bulk_item(const std::string& book, double sales_price,
                   std::size_t qty = 0, double disc_rate = 0.0):
                      Item_base(book, sales_price),
                      min_qty(qty), discount(disc_rate) { }
      };

2.4 在派生类构造函数中使用默认参数

这部分没看懂,怀疑code写错了。

Of course, we might write these two Bulk_item constructors as a single constructor that takes default arguments:

     class Bulk_item : public Item_base {
     public:
         Bulk_item(const std::string& book, double sales_price,
                   std::size_t qty = 0, double disc_rate = 0.0):
                      Item_base(book, sales_price),
                      min_qty(qty), discount(disc_rate) { }
         // as before
      };

Here we provide defaults for each parameter so that the constructor might be used with zero to four arguments.

怀疑 booksales_price都是有值的,但书上的code就这么写的。

2.5 只能初始化一个直接基类

假定类A派生类B,类B派生类C,那么初始化时,C初始化BB初始化C

重构:通过添加/删除操作和数据等操作重新设计类称为重构。

3 复制控制和继承

一个类,只要不含有指针,一般就能使用合成的操作,反之,通常需要自定义复制控制。

3.1 派生类的拷贝构造函数

如果派生类显式地自定义复制构造函数或赋值操作符,那么默认的复制构造函数或赋值操作符将被完全覆盖。派生类的复制构造函数和赋值操作符将负责对基类和和自己的类里的数据的复制或赋值。

如果派生类定义自己的复制构造函数,该复制构造函数通常应当显式地使用基类的复制构造函数以初始化对象的基类部分。

class Base { /* ... */ };
class Derived: public Base {
public:
    // Base::Base(const Base&) not invoked automatically
    Derived(const Derived& d): Base(d) { /*... */ }
};

初始化函数 Base(d) 将派生的对象 d 转化为基类部分的引用,并调用基类的复制构造函数。如果省略基类的初始化函数:

 // probably incorrect definition of the Derived copy constructor
 Derived(const Derived& d) /* derived member initizations */
                                   {/* ... */ }

结果就是,基类的默认构造函数会被调用,以初始化对象的基类部分。假定初始化派生类成员时从d复制了对应的元素, 那么新构建的对象就会很奇怪:它的基类部分值为默认值,它的派生类成员的值赋值了另一个对象。
(?? )

3.2 派生类的赋值操作符

赋值操作符和复制构造函数相似:如果派生类定义了它自己的赋值操作符,那么这个赋值操作符必须显示地对基类部分进行赋值:

// Base::operator=(const Base&) not invoked automatically
Derived &Derived::operator=(const Derived &rhs)
{
    if (this != &rhs) {
        Base::operator=(rhs); // assigns the base part
        // do whatever needed to clean up the old value in the derived part
        // assign the members from the derived
    }
    return *this;
}

3.3 派生类的析构函数

析构函数不同于复制构造函数和赋值操作符:派生类的析构函数从不负责基类对象成员的销毁。编译器总是隐式调用派生类对象的基类部分的析构函数。每一个析构函数只负责清理自己的类的成员清理。

class Derived: public Base {
public:
    // Base::~Base invoked automatically
    ~Derived()    { /* do what it takes to clean up derived members */ }
};

对象被销毁的顺序与它们被构建的顺序相反:派生类的析构函数先执行,直到最上层的基类。

4 虚拟析构函数

基类部分的构造函数是被自动调用的,这对基类的设计有重要影响。

当我们delete指向动态分配对象的指针时,析构函数先执行,以清理对象,然后才会释放对象所占用的内存空间。当处理继承体系结构中的对象时,指针的静态类型不同于被删除的对象的动态类型是可能的,我们有可能delete一个实际上指向派生类对象的基类类型指针。

假如我们delete一个指向基类的指针,那么基类的析构函数会执行,基类的成员被清理。如果对象实际上是一个派生类类型的,那么行为将是未定义的。为了保证正确的析构函数被执行,那么,基类中的析构函数必须是虚拟的。

class Item_base {
public:
    // no work, but virtual destructor needed
    // if base pointer that points to a derived object is ever deleted
    virtual ~Item_base() { }
};

如果析构函数是虚拟的,那么,当它通过指针被调用时,调用哪一个析构函数取决于指针实际所指对象的类型:

     Item_base *itemP = new Item_base; // same static and dynamic type
     delete itemP;          // ok: destructor for Item_base called
     itemP = new Bulk_item; // ok: static and dynamic types differ
     delete itemP;          // ok: destructor for Bulk_item called

就像其他虚拟函数一样,析构函数的虚拟特性被继承,所以,如果根类中的析构函数是虚拟的,那么派生类中的虚拟函数也是虚拟的。

构造函数和赋值virtual

只有析构函数能被定义为虚拟的。构造函数不允许,因为,构造函数运行的时候,对象还没有完全构建,对象的动态类型因此不完整。

虽然在基类中我们可以定义虚拟操作符=成员函数,但是这样定义不会影响派生类中使用的赋值操作符。每个类都有它自己的赋值操作符。派生类中的赋值操作符的参数同类本身。

将类赋值操作符定义为virtual的,很可能让人困惑, 并且毫无用处。

5 构造函数和析构函数中的虚拟

在构造函数运行时,或者在析构函数运行时,对象都是不完整的,然后讨论了在构造函数或析构函数中调用虚拟函数的问题,大段篇幅没看明白,但我应该不会写出这样的函数。

以上是 C++ Primer边看边做的翻译。


[1]C++ Primer 15.4 Constructors and Copy Control
[2]https://stackoverflow.com/questions/9979194/what-is-constructor-inheritance
[3]https://stackoverflow.com/questions/6178771/inheritance-and-constructors/6178801

猜你喜欢

转载自blog.csdn.net/ftell/article/details/80562798