linux C++ day11

目录:
覆盖的条件
严格区分重载、覆盖和隐藏
在构造和析构函数中调用虚函数
抽象基类
简单工厂模式
模板方法模式
子类对象的内存泄漏
虚析构函数
空虚析构函数
纯虚析构函数
1 覆盖的条件
1.1 问题
只有类的成员函数才能被声明为虚函数,全局函数和类的静态成员函数都不能被声明为虚函数。只有基类中被virtual关键字声明为虚函数的成员函数才能被子类覆盖。虚函数在子类中的覆盖版必须和该函数的基类版本拥有完全相同的签名,即函数名、形参表和常属性严格一致。如果基类中的虚函数返回基本类型的数据或类类型的对象,那么该函数在子类中的覆盖版本必须返回相同类型的数据或对象,否则将引发编译错误。如果基类中的虚函数返回类类型的指针或引用,那么该函数在子类中的覆盖版本可以返回其基类版本返回类型的公有子类的指针或引用。如果基类中的虚函数带有异常说明,那么该函数在子类中的覆盖版本不能说明比其基类版本抛出更多的异常。无论基类中的虚函数位于该类的公有、私有还是保护部分,该函数在子类中的覆盖版本都可以出现在该类包括公有、私有和保护在内的任何部分。

1.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:必须是成员函数

代码如下:

#include
class A
{
public:
virtual void foo (void)
{

}

// static virtual void staticNumb (void)
// {
//
// }
};
//virtual void global (void)
//{
//
//}
上述代码中,以下代码:

virtual void foo (void)
{
    
}

只有类的成员函数才能被声明为虚函数。

上述代码中,以下代码:

// static virtual void staticNumb (void)
// {
//
// }
类的静态成员函数不能被声明为虚函数。

上述代码中,以下代码:

//virtual void global (void)
//{
//
//}
全局函数也不能被声明为虚函数。

步骤二:函数签名必须相同

代码如下:

#include
class A
{
public:
virtual void foo (void)
{

}

// static virtual void staticNumb (void)
// {
//
// }
};
class B : public A
{
virtual void bar (void)
{

}
virtual void foo (float)
{
    
}
virtual void foo (void) const
{
    
}
void foo (void)
{
    
}

};
//virtual void global (void)
//{
//
//}
上述代码中,以下代码:

virtual void bar (void)

由于虚函数在子类中的覆盖版必须和该函数的基类版本拥有完全相同函数名,所以类B的成员函数bar不是类A中的虚函数foo的覆盖版。

上述代码中,以下代码:

virtual void foo (float)

由于虚函数在子类中的覆盖版必须和该函数的基类版本拥有完全相同形参表,所以类B的成员函数void foo(int)不是类A中的虚函数void foo(void)的覆盖版。

上述代码中,以下代码:

virtual void foo (void) const

由于虚函数在子类中的覆盖版必须和该函数的基类版本拥有完全相同常属性,所以类B的成员函数void foo(void)const不是类A中的虚函数void foo(void)的覆盖版。

上述代码中,以下代码:

void foo (void)

由于虚函数在子类中的覆盖版必须和该函数的基类版本拥有完全相同函数名、形参表和常属性,所以类B的成员函数void foo(void)是类A中的虚函数void foo(void)的覆盖版。

步骤三:返回同类型的基本类型或对象

代码如下:

#include
class X
{
public:
int m_x;
};
class A
{
public:
virtual void foo (void)
{

}
virtual int bar (void)
{
    return 0;
}
virtual X hum (void)
{
    return *new X;
}

// static virtual void staticNumb (void)
// {
//
// }
};
class B : public A
{
int bar (void)
{
return 0;
}
virtual void foo (float)
{

}
virtual void foo (void) const
{
    
}
void foo (void)
{
    
}
X hum (void)
{
    return *new X;
}

};
//virtual void global (void)
//{
//
//}
上述代码中,以下代码:

virtual int bar (void)
{
    return 0;
}

如果基类A中的虚函数bar返回基本类型int的数据,那么该函数在子类B中的覆盖版,如下语句所示:

int bar (void)
{
    return 0;
}

也必须返回相同类型的数据,即int型的数据。

上述代码中,以下代码:

virtual X hum (void)
{
    return *new X;
}

如果基类A中的虚函数hum返回类类型X的对象,那么该函数在子类B中的覆盖版本,如下语句所示:

X hum (void)
{
    return *new X;
}

必须返回相同类型的数据,即类X的对象。

步骤四:返回类的指针或引用允许协变

代码如下:

#include
class X
{
public:
int m_x;
X (const int& x = 0) : m_x(x)
{

}

};
class Y : public X
{
public:
Y (const int& y = 0) : X(y)
{

}

};
class A
{
public:
virtual void foo (void)
{

}
virtual int bar (void)
{
    return 0;
}
virtual X hum (void)
{
    return *new X;
}
virtual X* foo (int i)
{
    return new X(i);
}
virtual X& bar (int i)
{
    return *new X(i);
}

// static virtual void staticNumb (void)
// {
//
// }
};
class B : public A
{
int bar (void)
{
return 0;
}
virtual void foo (float)
{

}
virtual void foo (void) const
{
    
}
void foo (void)
{
    
}
X hum (void)
{
    return *new X;
}
virtual Y* foo (int i)
{
    return new Y(i);
}
virtual Y& bar (int i)
{
    return *new Y(i);
}

};
//virtual void global (void)
//{
//
//}
上述代码中,以下代码:

virtual X* foo (int i)
{
    return new X(i);
}

如果基类A中的虚函数foo返回类类型X的指针,那么该函数在子类中的覆盖版本,如下语句所示:

virtual Y* foo (int i)
{
    return new Y(i);
}

可以返回在基类A中的虚函数foo返回类类型X的公有子类Y的指针

上述代码中,以下代码:

virtual X& bar (int i)
{
    return *new X(i);
}

如果基类A中的虚函数foo返回类类型X的引用,那么该函数在子类中的覆盖版本,如下语句所示:

virtual Y& bar (int i)
{
    return *new Y(i);
}

可以返回在基类A中的虚函数foo返回类类型X的公有子类Y的引用。

步骤五:不能扩大异常说明

代码如下:

#include
class X
{
public:
int m_x;
X (const int& x = 0) : m_x(x)
{

}

};
class Y : public X
{
public:
Y (const int& y = 0) : X(y)
{

}

};
class A
{
public:
virtual void foo (void)
{

}
virtual int bar (void)
{
    return 0;
}
virtual X hum (void)
{
    return *new X;
}
virtual X* foo (int i)
{
    return new X(i);
}
virtual X& bar (int i)
{
    return *new X(i);
}
virtual void func (void) throw (int, std::string)
{
    
}

// static virtual void staticNumb (void)
// {
//
// }
};
class B : public A
{
int bar (void)
{
return 0;
}
virtual void foo (float)
{

}
virtual void foo (void) const
{
    
}
void foo (void)
{
    
}
X hum (void)
{
    return *new X;
}
virtual Y* foo (int i)
{
    return new Y(i);
}
virtual Y& bar (int i)
{
    return *new Y(i);
}
virtual void func (void) throw (int)
{
    
}

};
//virtual void global (void)
//{
//
//}
上述代码中,以下代码:

virtual void func (void) throw (int, std::string)
{
    
}

如果基类A中的虚函数func带有异常说明,那么该函数在子类B中的覆盖版本,如下语句所示:

virtual void func (void) throw (int)
{
    
}

不能说明比其基类版本抛出更多的异常。

步骤六:访控属性可以不同

代码如下:

#include
class X
{
public:
int m_x;
X (const int& x = 0) : m_x(x)
{

}

};
class Y : public X
{
public:
Y (const int& y = 0) : X(y)
{

}

};
class A
{
public:
virtual void foo (void)
{

}
virtual int bar (void)
{
    return 0;
}
virtual X hum (void)
{
    return *new X;
}
virtual X* foo (int i)
{
    return new X(i);
}
virtual X& bar (int i)
{
    return *new X(i);
}
virtual void func (void) throw (int, std::string)
{
    
}
virtual void access (void)
{
    
}

// static virtual void staticNumb (void)
// {
//
// }
};
class B : public A
{
private:
void access (void)
{

}

public:
int bar (void)
{
return 0;
}
virtual void foo (float)
{

}
virtual void foo (void) const
{
    
}
void foo (void)
{
    
}
X hum (void)
{
    return *new X;
}
virtual Y* foo (int i)
{
    return new Y(i);
}
virtual Y& bar (int i)
{
    return *new Y(i);
}
virtual void func (void) throw (int)
{
    
}

};
//virtual void global (void)
//{
//
//}
int main(int argc, const char * argv[])
{

A *p = new B;
p->access();

return 0;

}
上述代码中,以下代码:

virtual void access (void)
{
    
}

无论基类A中的虚函数access位于该类的公有、私有还是保护部分,该函数在子类B中的覆盖版本,如下语句所示:

private:
void access (void)
{

}

都可以出现在子类B包括公有、私有和保护在内的任何部分。只要在基类A中位于公有部分,即是在子类B中定义到了私有部分,仍然能实现多态,如以下主程序中语句:

A *p = new B;
p->access();

不会报编译错误。

1.3 完整代码
本案例的完整代码如下所示:

#include
class X
{
public:
int m_x;
X (const int& x = 0) : m_x(x)
{

}

};
class Y : public X
{
public:
Y (const int& y = 0) : X(y)
{

}

};
class A
{
public:
virtual void foo (void)
{

}
virtual int bar (void)
{
    return 0;
}
virtual X hum (void)
{
    return *new X;
}
virtual X* foo (int i)
{
    return new X(i);
}
virtual X& bar (int i)
{
    return *new X(i);
}
virtual void func (void) throw (int, std::string)
{
    
}
virtual void access (void)
{
    
}

// static virtual void staticNumb (void)
// {
//
// }
};
class B : public A
{
private:
void access (void)
{

}

public:
int bar (void)
{
return 0;
}
virtual void foo (float)
{

}
virtual void foo (void) const
{
    
}
void foo (void)
{
    
}
X hum (void)
{
    return *new X;
}
virtual Y* foo (int i)
{
    return new Y(i);
}
virtual Y& bar (int i)
{
    return *new Y(i);
}
virtual void func (void) throw (int)
{
    
}

};
//virtual void global (void)
//{
//
//}
int main(int argc, const char * argv[])
{

A *p = new B;
p->access();

return 0;

}
2 严格区分重载、覆盖和隐藏
2.1 问题
重载的函数必须在同一个作用域中,包括通过using声明引入到同一作用域的函数。覆盖的函数必须是虚函数,并且要满足上面讲的虚函数的一系列特殊条件。子类与基类的同名成员函数,不满足重载和覆盖的条件,且能正常通过编译,则必然构成隐藏。

2.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:严格区分重载、覆盖和隐藏

代码如下:

#include
class Base
{
public:
virtual void foo (void)
{
std::cout << “Base foo” << std::endl;
}
virtual void foo (void) const
{
std::cout << “Base foo const” << std::endl;
}
};
class Derived : public Base
{
public:
void foo (void)
{
std::cout << “Derived foo” << std::endl;
}
void foo (void) const
{
std::cout << “Derived foo const” << std::endl;
}
};
int main(int argc, const char * argv[])
{

Base *b = new Derived;
b->foo();
delete b;
Base const *b1 = new Derived;
b1->foo();
delete b1;

Derived d;
d.Base::foo();
d.foo();
Derived const d1 = d;
d1.Base::foo();
d1.foo();

return 0;

}
上述代码中,以下代码:

class Base
{
public:
virtual void foo (void)
{
std::cout << “Base foo” << std::endl;
}
virtual void foo (void) const
{
std::cout << “Base foo const” << std::endl;
}
};
定义了一个类Base,在该类的作用域中,重载了两个成员函数foo。

上述代码中,以下代码:

class Derived : public Base
{
public:
void foo (void)
{
std::cout << “Derived foo” << std::endl;
}
void foo (void) const
{
std::cout << “Derived foo const” << std::endl;
}
};
定义了一个子类Derived,继承自类Base,在子类Derived的作用域中,同样重载了两个成员函数foo。

对于子类Derived的成员函数foo,如下所示:

void foo (void)
{
    std::cout << "Derived foo" << std::endl;
}

由于与基类Base的同名成员函数foo,如下所示:

virtual void foo (void)
{
    std::cout << "Base foo" << std::endl;
}

不在一个作用域内,所以不满足重载条件,不是重载关系。又因为它们两个都是虚函数并且满足覆盖的条件,所以子类Derived中的foo覆盖基类Base中的foo。

又由于与基类Base的同名常函数foo,如下所示:

virtual void foo (void) const
{
    std::cout << "Base foo const" << std::endl;
}

不在一个作用域内,所以不满足重载条件,不是重载关系。虽然它们两个都是虚函数但是不满足覆盖的条件,不是覆盖关系。由于它们能正常通过编译,所以是隐藏关系。

2.3 完整代码
本案例的完整代码如下所示:

#include
class Base
{
public:
virtual void foo (void)
{
std::cout << “Base foo” << std::endl;
}
virtual void foo (void) const
{
std::cout << “Base foo const” << std::endl;
}
};
class Derived : public Base
{
public:
void foo (void)
{
std::cout << “Derived foo” << std::endl;
}
void foo (void) const
{
std::cout << “Derived foo const” << std::endl;
}
};
int main(int argc, const char * argv[])
{

Base *b = new Derived;
b->foo();
delete b;
Base const *b1 = new Derived;
b1->foo();
delete b1;

Derived d;
d.Base::foo();
d.foo();
Derived const d1 = d;
d1.Base::foo();
d1.foo();

return 0;

}
3 在构造和析构函数中调用虚函数
3.1 问题
在基类的构造和析构函数中调用虚函数,绝不可能表现出多态性。实际被调用的一定是基类的原始版本,而非子类的覆盖版本。因为基类的构造函数被子类的构造函数调用时,子类对象尚未构造完成。这时调用虚函数,它只能被绑定到基类版本。同样基类的析构函数被子类的析构函数调用时,子类对象已被释放。这时调用虚函数,它也只能被绑定到基类版本。

3.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:在构造和析构函数中调用虚函数

代码如下:

#include
class Base
{
public:
Base (void)
{
foo();
}
~Base (void)
{
foo();
}
virtual void foo (void)
{
std::cout << “Base foo” << std::endl;
}
};
class Derived : public Base
{
public:
void foo (void)
{
std::cout << “Derived foo” << std::endl;
}
};
int main(int argc, const char * argv[])
{

Base *b = new Derived;
delete b;

return 0;

}
上述代码中,以下代码:

Base (void)
{
    foo();
}
~Base (void)
{
    foo();
}

在基类Base中定义了它的构造函数与析构函数,在这两个函数中,分别调用了虚函数foo。虚函数foo在基类Base中的定义如下:

virtual void foo (void)
{
    std::cout << "Base foo" << std::endl;
}

虚函数foo在子类Derived中的定义如下:

void foo (void)
{
    std::cout << "Derived foo" << std::endl;
}

上述代码中,以下代码:

Base *b = new Derived;

在主程序中,定义了基类Base的指针b,让它指向堆上的子类Derived的对象。此时,new操作符为堆上的子类Derived的对象分配空间,首先分配子类Derived的对象的基类部分,基类部分分配完成后,即调用基类Base的构造函数,在基类的构造函数体中,如下代码所示:

Base (void)
{
    foo();
}

调用了虚函数foo,此时由于堆上的子类Derived的对象只分配了基类部分,子类Derived的扩展部分还没有被分配,所以此时虚函数foo只能被绑定到基类版本。

上述代码中,以下代码:

delete b;

在主程序中,释放了基类Base的指针b指向的堆上的子类Derived对象。此时,delete操作符首先调用子类Derived的析构函数,并释放子类Derived的对象的扩展部分,然后调用基类Base的析构函数,在基类的析构函数体中,如下代码所示:

~Base (void)
{
    foo();
}

调用了虚函数foo,此时由于堆上的子类Derived的对象已经释放了它的扩展部分,所以此时虚函数foo只能被绑定到基类版本。

3.3 完整代码
本案例的完整代码如下所示:

#include
class Base
{
public:
Base (void)
{
foo();
}
~Base (void)
{
foo();
}
virtual void foo (void)
{
std::cout << “Base foo” << std::endl;
}
};
class Derived : public Base
{
public:
void foo (void)
{
std::cout << “Derived foo” << std::endl;
}
};
int main(int argc, const char * argv[])
{

Base *b = new Derived;
delete b;

return 0;

}
4 抽象基类
4.1 问题
纯虚函数是如下定义的虚函数

class 类名

{ virtual 返回类型 函数名 (形参表) = 0;

};

纯虚函数因其所代表的抽象行为而无需或无法实现,包含此种函数的类亦因其所具有的一般性而表现出抽象的特征。至少包含一个纯虚函数的类称为抽象类,抽象类往往用来表示在对问题进行分析、设计的过程中所得出的抽象概念,是对一系列看上去不同,但本质上相同的具体概念的抽象。抽象类不能实例化为对象。抽象类的子类如果不对基类中的全部纯虚函数提供有效的覆盖,那么该子类就也是抽象类。

4.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:纯虚函数与抽象类

代码如下:

#include
class Abstract
{
public:
virtual void show (void) = 0;
};
定义了一个类Abstract的纯虚函数show。因为类Abstract中包含了一个纯虚函数,所以类Abstract被称为抽象类。

步骤二:抽象类不能实例化为对象

代码如下:

#include
class Abstract
{
public:
virtual void show (void) = 0;
};
int main(int argc, const char * argv[])
{

Abstract a;

return 0;

}
上述代码中,以下代码:

Abstract a;

会报编译错误。因为无论是直接定义,还是通过new运算符,抽象类永远不能实例化为对象。

步骤三:抽象类的子类

代码如下:

#include
class Abstract
{
public:
virtual void show (void) = 0;
};
class MidDerived : public Abstract
{
};
class Derived : public Abstract
{
public:
void show (void)
{
std::cout << “Derived show” << std::endl;
}
};
int main(int argc, const char * argv[])
{

Abstract a;
MidDerived m;
Derived d;

return 0;

}
上述代码中,以下代码:

class MidDerived : public Abstract
{
};
抽象类Abstract的子类MidDerived,如果不对基类Abstract中的全部纯虚函数提供有效的覆盖,那么该子类MidDerived就也是抽象类。在主程序中,如下代码:

MidDerived m;

同样会报编译错误。

上述代码中,以下代码:

class Derived : public Abstract
{
public:
void show (void)
{
std::cout << “Derived show” << std::endl;
}
};
抽象类Abstract的子类Derived,如果对基类Abstract中的全部纯虚函数提供有效的覆盖,那么该子类Derived就不再是抽象类。在主程序中,如下代码:

Derived d;

是正确的。

4.3 完整代码
本案例的完整代码如下所示:

#include
class Abstract
{
public:
virtual void show (void) = 0;
};
class MidDerived : public Abstract
{
};
class Derived : public Abstract
{
public:
void show (void)
{
std::cout << “Derived show” << std::endl;
}
};
int main(int argc, const char * argv[])
{

Abstract a;
MidDerived m;
Derived d;

return 0;

}
5 简单工厂模式
5.1 问题
全部由纯虚函数构成的抽象类称为纯抽象类或接口。面向抽象编程,使得所有基于接口编写的代码,在子类被更替后,无需做任何修改或只需做很少的修改,就能在新子类上正确运行。

5.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:简单工厂模式

代码如下:

#include
typedef enum ProductTypeTag
{
TypeA,
TypeB,
TypeC
}PRODUCTTYPE;
// Here is the product class
class Product
{
public:
virtual void show() = 0;
};
class ProductA : public Product
{
public:
void show(void)
{
std::cout << “I’m ProductA” << std::endl;
}
};
class ProductB : public Product
{
public:
void show(void)
{
std::cout << “I’m ProductB” << std::endl;
}
};
class ProductC : public Product
{
public:
void show(void)
{
std::cout << “I’m ProductC” << std::endl;
}
};
// Here is the Factory class
class Factory
{
public:
Product* CreateProduct(PRODUCTTYPE type)
{
switch (type)
{
case TypeA:
return new ProductA;

        case TypeB:
            return new ProductB;
            
        case TypeC:
            return new ProductC;
            
        default:
            return NULL;
    }
}

};
int main(int argc, char *argv[])
{
// First, create a factory object
Factory *ProductFactory = new Factory();
Product *productObjA = ProductFactory->CreateProduct(TypeA);
if (productObjA != NULL)
productObjA->show();

Product *productObjB = ProductFactory->CreateProduct(TypeB);
if (productObjB != NULL)
    productObjB->show();

Product *productObjC = ProductFactory->CreateProduct(TypeC);
if (productObjC != NULL)
    productObjC->show();

delete ProductFactory;
delete productObjA;
delete productObjB;
delete productObjC;

return 0;

}
上述代码中,以下代码:

class Product
{
public:
virtual void show() = 0;
};
定义了一个抽象产品类Product。

上述代码中,以下代码:

class ProductA : public Product
{
public:
void show(void)
{
std::cout << “I’m ProductA” << std::endl;
}
};
class ProductB : public Product
{
public:
void show(void)
{
std::cout << “I’m ProductB” << std::endl;
}
};
class ProductC : public Product
{
public:
void show(void)
{
std::cout << “I’m ProductC” << std::endl;
}
};
定义了3个具体产品类。在这3个类中,分别对纯虚函数show进行了实现。还可能有更多的具体产品类。这样就造成创建对象时,对象多而杂。

上述代码中,以下代码:

class Factory
{
public:
Product* CreateProduct(PRODUCTTYPE type)
{
switch (type)
{
case TypeA:
return new ProductA;

        case TypeB:
            return new ProductB;
            
        case TypeC:
            return new ProductC;
            
        default:
            return NULL;
    }
}

};
定义了一个简单工厂类Factory,其中含有一个成员函数CreateProduct用于生成具体产品对象,以简化创建对象时,对象多而杂的现象。这样就能把对象的创建和操作两部分分离开,方便后期的程序扩展和维护。

上述代码中,以下代码:

Factory *ProductFactory = new Factory();
Product *productObjA = ProductFactory->CreateProduct(TypeA);
if (productObjA != NULL)
    productObjA->show();

在主程序中,使用简单工厂类Factory的CreateProduct函数生成具体产品对象,用抽象基类Product的指针实现多态。

5.3 完整代码
本案例的完整代码如下所示:

#include
typedef enum ProductTypeTag
{
TypeA,
TypeB,
TypeC
}PRODUCTTYPE;
// Here is the product class
class Product
{
public:
virtual void show() = 0;
};
class ProductA : public Product
{
public:
void show(void)
{
std::cout << “I’m ProductA” << std::endl;
}
};
class ProductB : public Product
{
public:
void show(void)
{
std::cout << “I’m ProductB” << std::endl;
}
};
class ProductC : public Product
{
public:
void show(void)
{
std::cout << “I’m ProductC” << std::endl;
}
};
// Here is the Factory class
class Factory
{
public:
Product* CreateProduct(PRODUCTTYPE type)
{
switch (type)
{
case TypeA:
return new ProductA;

        case TypeB:
            return new ProductB;
            
        case TypeC:
            return new ProductC;
            
        default:
            return NULL;
    }
}

};
int main(int argc, char *argv[])
{
// First, create a factory object
Factory *ProductFactory = new Factory();
Product *productObjA = ProductFactory->CreateProduct(TypeA);
if (productObjA != NULL)
productObjA->show();

Product *productObjB = ProductFactory->CreateProduct(TypeB);
if (productObjB != NULL)
    productObjB->show();

Product *productObjC = ProductFactory->CreateProduct(TypeC);
if (productObjC != NULL)
    productObjC->show();

delete ProductFactory;
delete productObjA;
delete productObjB;
delete productObjC;

return 0;

}
6 模板方法模式
6.1 问题
全部由纯虚函数构成的抽象类称为纯抽象类或接口。面向抽象编程,使得所有基于接口编写的代码,在子类被更替后,无需做任何修改或只需做很少的修改,就能在新子类上正确运行。

6.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:模板方法模式

代码如下:

#include
class Abstract
{
protected:
virtual void PrimitiveOperation1() = 0;
virtual void PrimitiveOperation2() = 0;
public:
void TemplateMethod()
{
std::cout << “TemplateMethod” << std::endl;
PrimitiveOperation1();
PrimitiveOperation2();
}
};
class ConcreteA : public Abstract
{
protected:
virtual void PrimitiveOperation1()
{
std::cout << “ConcreteA Operation1” << std::endl;
}
virtual void PrimitiveOperation2()
{
std::cout << “ConcreteA Operation2” << std::endl;
}
};
class ConcreteB : public Abstract
{
protected:
virtual void PrimitiveOperation1()
{
std::cout << “ConcreteB Operation1” << std::endl;
}
virtual void PrimitiveOperation2()
{
std::cout << “ConcreteB Operation2” << std::endl;
}
};
int main()
{
Abstract *pAbstractA = new ConcreteA;
pAbstractA->TemplateMethod();

Abstract *pAbstractB = new ConcreteB;
pAbstractB->TemplateMethod();

delete pAbstractA;
delete pAbstractB;

}
上述代码中,以下代码:

class Abstract
{
protected:
virtual void PrimitiveOperation1() = 0;
virtual void PrimitiveOperation2() = 0;
public:
void TemplateMethod()
{
std::cout << “TemplateMethod” << std::endl;
PrimitiveOperation1();
PrimitiveOperation2();
}
};
定义了抽象模板类Abstract。在该类中,定义两个抽象的原语操作纯虚函数PrimitiveOperation1和纯虚函数PrimitiveOperation2,用这两个原语操作定义一个抽象算法的两个步骤。在该类中还定义了一个模板成员函数TemplateMethod负责整合两个原语操作及其它一些操作,形成一个不变的算法流程。

上述代码中,以下代码:

class ConcreteA : public Abstract
{
protected:
virtual void PrimitiveOperation1()
{
std::cout << “ConcreteA Operation1” << std::endl;
}
virtual void PrimitiveOperation2()
{
std::cout << “ConcreteA Operation2” << std::endl;
}
};
class ConcreteB : public Abstract
{
protected:
virtual void PrimitiveOperation1()
{
std::cout << “ConcreteB Operation1” << std::endl;
}
virtual void PrimitiveOperation2()
{
std::cout << “ConcreteB Operation2” << std::endl;
}
};
定义了两个实体类,在这两个实体类中,各自实现两个具体的原语操作,完成各自实体中的具体算法的各步骤的具体操作。这样如果再有其它的具体算法,只需要再定义一个实体类,实现具体的原语操作即可。

上述代码中,以下代码:

Abstract *pAbstractA = new ConcreteA;
pAbstractA->TemplateMethod();

在主程序中,用抽象模板类Abstract的指针指向实体类ConcreteA,完成一个具体的算法。

6.3 完整代码
本案例的完整代码如下所示:

#include
class Abstract
{
protected:
virtual void PrimitiveOperation1() = 0;
virtual void PrimitiveOperation2() = 0;
public:
void TemplateMethod()
{
std::cout << “TemplateMethod” << std::endl;
PrimitiveOperation1();
PrimitiveOperation2();
}
};
class ConcreteA : public Abstract
{
protected:
virtual void PrimitiveOperation1()
{
std::cout << “ConcreteA Operation1” << std::endl;
}
virtual void PrimitiveOperation2()
{
std::cout << “ConcreteA Operation2” << std::endl;
}
};
class ConcreteB : public Abstract
{
protected:
virtual void PrimitiveOperation1()
{
std::cout << “ConcreteB Operation1” << std::endl;
}
virtual void PrimitiveOperation2()
{
std::cout << “ConcreteB Operation2” << std::endl;
}
};
int main()
{
Abstract *pAbstractA = new ConcreteA;
pAbstractA->TemplateMethod();

Abstract *pAbstractB = new ConcreteB;
pAbstractB->TemplateMethod();

delete pAbstractA;
delete pAbstractB;

}
7 子类对象的内存泄漏
7.1 问题
当使用delete运算符释放一个指向子类对象的基类指针时,因为基类指针只能访问子类对象中的基类子对象,所以实际被调用的仅仅是基类的析构函数,而基类的析构函数只负责析构子类对象中的基类子对象,不会调用子类的析构函数,这样就造成在子类中分配的资源将形成内存泄漏。

7.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:子类对象的内存泄漏

代码如下所示:

#include
class Human
{
private:
std::string m_name;
int m_age;
public:
Human (std::string const& name = “”, const int& age = 0) : m_name(name), m_age(age)
{
}
~Human (void)
{
std::cout << “Human destructor” << std::endl;
}
};
class Student : public Human
{
private:
int m_no;
char* m_address;
public:
Student (const int& no, const char* address) : m_no(no)
{
m_address = new char[strlen(address) + 1];
strcpy(m_address, address);
}
~Student (void)
{
delete [] m_address;
std::cout << “Student destructor” << std::endl;
}
};
int main(int argc, const char * argv[])
{

Human *pHuman = new Student(123456, "江苏南京市");
delete pHuman;

return 0;

}
上述代码中,以下代码:

~Human (void)
{
    std::cout << "Human destructor" << std::endl;
}

定义了类Human的析构函数,在该函数中,只是输出一句话,提示该函数被调用了。

上述代码中,以下代码:

class Student : public Human
{
private:
int m_no;
char* m_address;
public:
Student (const int& no, const char* address) : m_no(no)
{
m_address = new char[strlen(address) + 1];
strcpy(m_address, address);
}
在子类Student中定义了一个字符指针m_address,并在构造函数中,为其分配存储空间。

上述代码中,以下代码:

~Student (void)
{
    delete [] m_address;
    std::cout << "Student destructor" << std::endl;
}

由于在子类Student的构造函数中给m_address分配了存储空间,所以在析构函数中需要将其释放。

上述代码中,以下代码:

Human *pHuman = new Student(123456, "江苏南京市");

定义了一个基类的指针pHuman,让它指向一个子类Student在堆上的对象。这是没有问题的。因为基类的指针可以指向子类的对象。但此时该指针只能访问子类中继承过来的基类的子对象,子类中自己扩展的内容该指针无法访问。这就造成了一个问题,如下主程序中的语句:

delete pHuman;

当用delete释放pHuman指向的对象时,只会调用子类中继承过来的基类部分的析构函数,释放子类中继承过来的基类部分,而不会调用子类的析构函数。如上所述,子类的析构函数要释放m_address指向的空间,不调用子类的析构函数,就意味着内存泄露。

7.3 完整代码
本案例的完整代码如下所示:

#include
class Human
{
private:
std::string m_name;
int m_age;
public:
Human (std::string const& name = “”, const int& age = 0) : m_name(name), m_age(age)
{
}
~Human (void)
{
std::cout << “Human destructor” << std::endl;
}
};
class Student : public Human
{
private:
int m_no;
char* m_address;
public:
Student (const int& no, const char* address) : m_no(no)
{
m_address = new char[strlen(address) + 1];
strcpy(m_address, address);
}
~Student (void)
{
delete [] m_address;
std::cout << “Student destructor” << std::endl;
}
};
int main(int argc, const char * argv[])
{

Human *pHuman = new Student(123456, "江苏南京市");
delete pHuman;

return 0;

}
8 虚析构函数
8.1 问题
如果将基类的析构函数声明为虚函数,那么当使用delete运算符释放一个指向子类对象的基类指针时,实际被调用的将是子类的析构函数。而子类的析构函数将首先析构子类对象的扩展部分,然后再通过基类的析构函数析构该对象的基类部分,最终实现完美的资源释放。

8.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:虚析构函数

代码如下:

#include
class Human
{
private:
std::string m_name;
int m_age;
public:
Human (std::string const& name = “”, const int& age = 0) : m_name(name), m_age(age)
{
}
virtual ~Human (void)
{
std::cout << “Human destructor” << std::endl;
}
};
class Student : public Human
{
private:
int m_no;
char* m_address;
public:
Student (const int& no, const char* address) : m_no(no)
{
m_address = new char[strlen(address) + 1];
strcpy(m_address, address);
}
~Student (void)
{
delete [] m_address;
std::cout << “Student destructor” << std::endl;
}
};
int main(int argc, const char * argv[])
{

Human *pHuman = new Student(123456, "江苏南京市");
delete pHuman;

return 0;

}
上述代码中,以下代码:

virtual ~Human (void)
{
    std::cout << "Human destructor" << std::endl;
}

定义了类Human的析构函数,并将该析构函数说明成了虚析构函数。当一个类的析构函数被说名称虚函数后,其向下任何一代派生类中的析构函数都将是虚函数,无论是否添加virtual关键字。

上述代码中,以下代码:

~Student (void)
{
    delete [] m_address;
    std::cout << "Student destructor" << std::endl;
}

由于在子类Student的构造函数中给m_address分配了存储空间,所以在析构函数中需要将其释放。又因为基类Human中的析构函数是虚函数,所以子类Student的析构函数也是虚函数。

上述代码中,以下代码:

Human *pHuman = new Student(123456, "江苏南京市");

定义了一个基类的指针pHuman,让它指向一个子类Student在堆上的对象。此时该指针由于析构函数是虚函数,就能访问子类中析构函数,如下主程序中的语句:

delete pHuman;

当用delete释放pHuman指向的对象时,实际被调用的将是子类Student的析构函数。而子类的析构函数将首先析构子类对象的扩展部分,然后再通过基类Human的析构函数析构该对象的基类部分。这样就不会造成子类Student的扩展部分申请的内存空间m_address被泄露了。

8.3 完整代码
本案例的完整代码如下所示:

#include
class Human
{
private:
std::string m_name;
int m_age;
public:
Human (std::string const& name = “”, const int& age = 0) : m_name(name), m_age(age)
{
}
virtual ~Human (void)
{
std::cout << “Human destructor” << std::endl;
}
};
class Student : public Human
{
private:
int m_no;
char* m_address;
public:
Student (const int& no, const char* address) : m_no(no)
{
m_address = new char[strlen(address) + 1];
strcpy(m_address, address);
}
~Student (void)
{
delete [] m_address;
std::cout << “Student destructor” << std::endl;
}
};
int main(int argc, const char * argv[])
{

Human *pHuman = new Student(123456, "江苏南京市");
delete pHuman;

return 0;

}
9 空虚析构函数
9.1 问题
对于一个没有分配任何资源的类,无需定义析构函数。但这样的类,编译器会为其提供一个缺省析构函数,而缺省析构函数并不是虚函数。为了保证当delete指向子类对象的基类指针时,能够正确调用子类的析构函数,就必须把基类的析构函数定义为虚函数,即使它是一个空函数。任何时候,为基类定义一个虚析构函数总是无害的。

9.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:空虚析构函数

代码如下所示:

#include
class Human
{
private:
std::string m_name;
int m_age;
public:
Human (std::string const& name = “”, const int& age = 0) : m_name(name), m_age(age)
{
}
virtual ~Human (void)
{
std::cout << “Human destructor” << std::endl;
}
};
class Student : public Human
{
private:
int m_no;
char* m_address;
public:
Student (const int& no, const char* address) : m_no(no)
{
m_address = new char[strlen(address) + 1];
strcpy(m_address, address);
}
~Student (void)
{
delete [] m_address;
std::cout << “Student destructor” << std::endl;
}
};
int main(int argc, const char * argv[])
{

Human *pHuman = new Student(123456, "江苏南京市");
delete pHuman;

return 0;

}
上述代码中,以下代码:

virtual ~Human (void)
{
    std::cout << "Human destructor" << std::endl;
}

定义了类Human的析构函数,并将该析构函数说明成了虚析构函数。当一个类的析构函数被说名称虚函数后,其向下任何一代派生类中的析构函数都将是虚函数,无论是否添加virtual关键字。我们可以注意到,再该析构函数当中的语句,是可有可无的,只是一个调试用的提示语句。如果将该语句删除掉,则析构函数就是空析构函数。

上述代码中,以下代码:

~Student (void)
{
    delete [] m_address;
    std::cout << "Student destructor" << std::endl;
}

由于在子类Student的构造函数中给m_address分配了存储空间,所以在析构函数中需要将其释放。又因为基类Human中的析构函数是虚函数,所以子类Student的析构函数也是虚函数。

上述代码中,以下代码:

Human *pHuman = new Student(123456, "江苏南京市");

定义了一个基类的指针pHuman,让它指向一个子类Student在堆上的对象。此时该指针由于析构函数是虚函数,就能访问子类中析构函数,如下主程序中的语句:

delete pHuman;

当用delete释放pHuman指向的对象时,实际被调用的将是子类Student的析构函数。而子类的析构函数将首先析构子类对象的扩展部分,然后再通过基类Human的析构函数析构该对象的基类部分。这样就不会造成子类Student的扩展部分申请的内存空间m_address被泄露了。

9.3 完整代码
本案例的完整代码如下所示:

#include
class Human
{
private:
std::string m_name;
int m_age;
public:
Human (std::string const& name = “”, const int& age = 0) : m_name(name), m_age(age)
{
}
virtual ~Human (void)
{
std::cout << “Human destructor” << std::endl;
}
};
class Student : public Human
{
private:
int m_no;
char* m_address;
public:
Student (const int& no, const char* address) : m_no(no)
{
m_address = new char[strlen(address) + 1];
strcpy(m_address, address);
}
~Student (void)
{
delete [] m_address;
std::cout << “Student destructor” << std::endl;
}
};
int main(int argc, const char * argv[])
{

Human *pHuman = new Student(123456, "江苏南京市");
delete pHuman;

return 0;

}
10 纯虚析构函数
10.1 问题
如果在一个需要体现出抽象性的类中,实在找不到更适合被定义为纯虚函数的成员函数,那么不妨把它的析构函数定义为纯虚函数。与普通纯虚函数不同的是,纯虚析构函数必须有定义,因为子类的析构函数总要调用基类的析构函数。

10.2 步骤
实现此案例需要按照如下步骤进行。

步骤一:纯虚析构函数

代码如下所示:

#include
class Human
{
private:
std::string m_name;
int m_age;
public:
Human (std::string const& name = “”, const int& age = 0) : m_name(name), m_age(age)
{
}
virtual ~Human (void) = 0;
};
Human::~Human (void)
{
std::cout << “Human destructor” << std::endl;
}
class Student : public Human
{
private:
int m_no;
char* m_address;
public:
Student (const int& no, const char* address) : m_no(no)
{
m_address = new char[strlen(address) + 1];
strcpy(m_address, address);
}
~Student (void)
{
delete [] m_address;
std::cout << “Student destructor” << std::endl;
}
};
int main(int argc, const char * argv[])
{

Human *pHuman = new Student(123456, "江苏南京市");
delete pHuman;

return 0;

}
上述代码中,以下代码:

virtual ~Human (void) = 0;

定义了类Human的纯虚析构函数。当一个类的析构函数被说名称纯虚函数后,其向下任何一代派生类中的析构函数都将是虚函数,无论是否添加virtual关键字。但是与普通纯虚函数不同的是,纯虚析构函数必须有定义,如下代码所示:

Human::~Human (void)
{
std::cout << “Human destructor” << std::endl;
}
因为子类的析构函数总要调用基类的析构函数。

上述代码中,以下代码:

~Student (void)
{
    delete [] m_address;
    std::cout << "Student destructor" << std::endl;
}

由于在子类Student的构造函数中给m_address分配了存储空间,所以在析构函数中需要将其释放。又因为基类Human中的析构函数是虚函数,所以子类Student的析构函数也是虚函数。

上述代码中,以下代码:

Human *pHuman = new Student(123456, "江苏南京市");

定义了一个基类的指针pHuman,让它指向一个子类Student在堆上的对象。此时该指针由于析构函数是虚函数,就能访问子类中析构函数,如下主程序中的语句:

delete pHuman;

当用delete释放pHuman指向的对象时,实际被调用的将是子类Student的析构函数。而子类的析构函数将首先析构子类对象的扩展部分,然后再通过基类Human的析构函数析构该对象的基类部分。这样就不会造成子类Student的扩展部分申请的内存空间m_address被泄露了。

10.3 完整代码
本案例的完整代码如下所示:

#include
class Human
{
private:
std::string m_name;
int m_age;
public:
Human (std::string const& name = “”, const int& age = 0) : m_name(name), m_age(age)
{
}
virtual ~Human (void) = 0;
};
Human::~Human (void)
{
std::cout << “Human destructor” << std::endl;
}
class Student : public Human
{
private:
int m_no;
char* m_address;
public:
Student (const int& no, const char* address) : m_no(no)
{
m_address = new char[strlen(address) + 1];
strcpy(m_address, address);
}
~Student (void)
{
delete [] m_address;
std::cout << “Student destructor” << std::endl;
}
};
int main(int argc, const char * argv[])
{

Human *pHuman = new Student(123456, "江苏南京市");
delete pHuman;

return 0;

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值