Effective C++ 2e Item41

类静态指针的申请和释放 <br />     如果类有一个静态成员指针变量,在进程中执行new或malloc操作分配了内存,进程退出前并不需要调用new或malloc,因为系统会自动进行释放。但这个静态成员指针变量需要多少分配,又应该如果处理呢,最近在项目代码中看到这样的例子,和大家分享一下。<br /> <br />    #include <unistd.h> #include <stdio.h> class CPointer { public: CPointer(){}; 阅读详情

条款41: 区分继承和模板

考虑下面两个设计问题:

· 作为一位立志献身计算机科学的学生,你想设计一个类来表示对象的堆栈。这将需要多个不同的类,因为每个堆栈中的元素必须是同类的,即,它里面包含的必须只是同种类型的对象。例如,会有一个类来表示int的堆栈,第二个类来表示string的堆栈,第三个类来表示string的堆栈的堆栈,等等。你也许对设计一个最小的类接口(参见条款18)很感兴趣,所以会将对堆栈的操作限制在:创建堆栈,销毁堆栈,将对象压入堆栈,将对象弹出堆栈,以及检查堆栈是否为空。设计中,你不会借助标准库中的类(包括stack ---- 参见条款49),因为你渴望亲手写这些代码。重用(Reuse)是一件美事,但当你的目标是探究事情的工作原理时,那就只有挖地三尺了。

· 作为一位爱猫的宠物迷,你想设计一个类来表示猫。这也将需要多个不同的类,因为每个品种的猫都会有点不同。和所有对象一样,猫可以被创建和销毁,但,正如所有猫迷所知道的,猫所做的其它事不外乎吃和睡。然而,每一种猫吃和睡都有各自惹人喜爱的方式。

这两个问题的说明听起来很相似,但却导致完全不同的两种设计。为什么?

答案涉及到"类的行为" 和 "类所操作的对象的类型"之间的关系。对于堆栈和猫来说,要处理的都是各种不同的类型(堆栈包含类型为T的对象,猫则为品种T),但你必须问自己这样一个问题:类型T影响类的行为吗?如果T不影响行为,你可以使用模板。如果T影响行为,你就需要虚函数,从而要使用继承。

下面的代码通过定义一个链表来实现Stack类,假设堆栈的对象类型为T:

class Stack {
public:
  Stack();
  ~Stack();

  void push(const T& object);
  T pop();

  bool empty() const;             // 堆栈为空?

private:
  struct StackNode {              // 链表节点
    T data;                       // 此节点数据
    StackNode *next;              // 链表中下一节点

    // StackNode构造函数,初始化两个域
    StackNode(const T& newData, StackNode *nextNode)
    : data(newData), next(nextNode) {}
  };

  StackNode *top;                 // 堆栈顶部

  Stack(const Stack& rhs);               // 防止拷贝和
  Stack& operator=(const Stack& rhs);    // 赋值(见条款27)
};

于是,Stack对象将构造如下所示的数据结构:

Stack对象 top--> data+next--> data+next--> data+next--> data+next
                        ------------------------------------------------------------------------------------
                                                 StackNode对象

链表本身是由StackNode对象构成的,但那只是Stack类的一个实现细节,所以StackNode被声明为Stack的私有类型。注意StackNode有一个构造函数,用来确保它所有的域都被正确初始化。即使你闭着眼睛都可以写出一个链表,但也不要忽视了C++的一些新特性,如struct中的构造函数。

下面看看你对Stack成员函数的实现。和许多原型(prototype)的实现(离制作成软件产品相差太远)一样,这里没有错误检查,因为在原型世界里,没有东西会出错。

Stack::Stack(): top(0) {}      // 顶部初始化为null

void Stack::push(const T& object)
{
  top = new StackNode(object, top);    // 新节点放在
}                                      // 链表头部

T Stack::pop()
{
  StackNode *topOfStack = top;    // 记住头节点
  top = top->next;

  T data = topOfStack->data;      // 记住节点数据
  delete topOfStack;

  return data;
}

Stack::~Stack()                   // 删除堆栈中所有对象
{
  while (top) {
    StackNode *toDie = top;       // 得到头节点指针
    top = top->next;              // 移向下一节点
    delete toDie;                 // 删除前面的头节点
  }
}

bool Stack::empty() const
{ return top == 0; }

这些代码毫无吸引人之处。实际上,唯一有趣的一点在于:即使对T一无所知,你还是能够写出每个成员函数。(上面的代码中实际上有个假设,即,假设可以调用T的拷贝构造函数;但正如条款45所说明的,这是一个绝对合理的假设)不管T是什么,对构造,销毁,压栈,出栈,确定栈是否为空等操作所写的代码不会变。除了 "可以调用T的拷贝构造函数" 这一假设外,stack的行为在任何地方都不依赖于T。这就是模板类的特点:行为不依赖于类型。

将stack类转化成一个模板就很简单了,即使是Dilbert的老板都会写:

template<class T> class Stack {

  ...                          // 完全和上面相同

};

但是,猫呢?为什么猫不适合模板?

重读上面的说明,注意这一条:"每一种猫吃和睡都有各自惹人喜爱的方式"。这意味着必须为每种不同的猫实现不同的行为。不可能写一个函数来处理所有的猫,所能做的只能是制定一个函数接口,所有种类的猫都必须实现它。啊哈!衍生一个函数接口的方法只能是去声明一个纯虚函数(参见条款36):

class Cat {
public:
  virtual ~Cat();                     // 参见条款14

  virtual void eat() = 0;             // 所有的猫吃食
  virtual void sleep() = 0;           // 所有的猫睡觉
};

Cat的子类 ---- 比如,Siamese和BritishShortHairedTabby ---- 当然得重新定义继承而来的eat和sleep函数接口:

class Siamese: public Cat {
public:
  void eat();
  void sleep();

  ...

};

class BritishShortHairedTabby: public Cat {
public:
  void eat();
  void sleep();

  ...

};

好了,现在知道了为什么模板适合Stack类而不适合Cat类,也知道了为什么继承适合Cat类。唯一剩下的问题是,为什么继承不适合Stack类。想知道为什么,不妨试着去声明一个Stack层次结构的根类 ---- 所有其它的堆栈类都从这个唯一的类继承:

class Stack {      // a stack of anything
public:
  virtual void push(const ??? object) = 0;
  virtual ??? pop() = 0;

  ...

};

现在问题很明显了。该为纯虚函数push和pop声明什么类型呢?记住,每一个子类必须重新声明继承而来的虚函数,而且参数类型和返回类型都要和基类的声明完全相同。不幸的是,一个int堆栈只能压入和弹出int对象,而一个Cat堆栈只能压入和弹出Cat对象。Stack类要怎样声明它的纯虚函数才能使用户既可以创建出int堆栈又可以创建出Cat堆栈呢?冷酷而严峻的事实是,做不到。这就是为什么说继承不适合创建堆栈。

但也许你做事喜欢偷偷摸摸。或许你认为自己可以通过使用通用(void*)指针来骗过编译器。但事实证明,现在这种情况下,通用指针也帮不上忙。因为你无法避开这一条件:派生类虚函数的声明永远不能和它在基类中的声明相抵触。但是,通用指针可以帮助解决另外一个不同的问题,它和模板所生成的类的效率有关。详细介绍参见条款42。

讲完了堆栈和猫,下面将本条款得到的结论总结如下:

· 当对象的类型不影响类中函数的行为时,就要使用模板来生成这样一组类。
· 当对象的类型影响类中函数的行为时,就要使用继承来得到这样一组类。

真正消化了以上两点的含义,你就可以在设计中游刃于继承或模板之间。

boost如何序列化 和反序列化 指针类(本身类也可以) 今天找了很长时间如何序列化自己类,但是找了很多帖子都没有序列化自己的例子。最后我感觉是没有重载复制函数的原因,当我增加复制函数后就可以序列化自己的类了。不多说了 直接上代码,希望能帮助其他朋友 class FPDF_BOOKMARKNODE { public: FPDF_BOOKMARKNODE() {} FPDF_BOOKMARKNODE(const std::wstring 阅读详情

相关推荐

写dial类,使用painter重绘表盘和指针

学程序首先要输出hell world,学电子要先来个流水灯。学Qt,那就必须先来个自定义控件,若有人问我从哪个下手,我推荐仪表盘,可简可繁,从低配到高配齐全,可入门也可进阶。 1、仪表盘解析 以常见的、传统的仪表盘为例,分解仪表盘的元素,主要有边框、刻度、数字、指针(或数字值、代替指针或二者都有)。边框就是背景图,可以动态的绘出也可以用一个图片贴图,指针可以绘出也可以用图片旋转来实现。其他的有刻度、数字、以及随着数值变化而引起的特效。 2、贴图的仪表盘 贴图仪表盘比较简单。实现方式:先找好2张图,一

li123128的博客 649

继承与模板的区别

体会:继承是数据的复制、模版是代码的复制。 模板函数在编译完成之后,会生成对应参数数类型的函数; 继承是对虚表、数据的复制,其中也涉及到使用虚函数继承产生多态时,虚表函数的替换; template<class T> void Sort(T arr, int nLength) { int i; int k; for (i = 0;i < nLength - 1;i++...

生命不息 折腾不止 2125

C++基础语法:类,结构体,指针和引用

C++在C语言的基础上,增加了面向对象编程,C++支持面向对象程序设计。类是C++的核心特性,定义一个类,本质上是定义一个数据类型的蓝图: 可以看见一段代码如下所示: #include <iostream> using namespace std; class Person{ private: int age, height; double money; string books[100];

baiyechangjiu的博客 757

带你了解c++中的继承和模板

1.什么是继承呢 继承就是一个类继承另一个类 然后这个类就他他继承类的成员(类型)和行为(函数) 继承的实质: 父类(基类)当中的属性,子类(派生类)中也有一份,这一份的属性是由继承方式的决定的。 继承的方法 class 子类名: 继承方式 父类名 { }; //继承方式: 权限限定词 //public 公有 //protected: 保护 //private: 私有继承 class 派生类名:继承方式 基类名 { //生成新的属性和行为 }; 1.1继承权限问题..

weixin_56366633的博客 1009

【C++】智能指针类和OpenCV的Ptr模板类

智能指针类引用计数智能指针(smart pointer)的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。引用计数为0时,删除对象。 其基本使用规则是: 每次创建类的新对象时,初始化指针并将引用计数置为1。当对象作为另一对象的副本而创建时,复制构造函数复制指针并增加与之相应的引用计数的值

Jason Ding的专栏 4271

C++ 【类和对象:this指针,构造函数 --1】

类和对象,this指针,构造函数

我心如火 3463

【C++】继承与模板

继承、多继承与菱形继承等问题的概念与代码示例。 模板的特化

Long_kin的博客 1649

MFC TabControl插件 非对话框类中获得主窗口和子窗口的指针

前言 在非对话框类中无法使用如(CParentDlg*)或者(CChildDlg*)进行强制转换。 所以一般通过一下来代码来实现通过获取CParentDlg的指针进行操作: CWnd* m_pParentDialog = AfxGetApp()->m_pMainWnd; //Get parent dialog pointer directly CEdit* pEdit = (CEdit*)m_pParentDialog->GetDlgItem(/*Edit Item ID*/); pEdit-&

lmnwsy的博客 681

C++——类和对象初阶讲解完整版(类的引入、类的定义、类的作用域、类的实例化、类访问限定符及封装、类成员函数的this指针、析构函数、构造函数、拷贝构造函数、赋值操作符重载、类的静态成员......)

家人们,百闻不如一见,百见不如一品啊!三万字超详细的C++类和对象讲解你值得拥有!

weixin_53811859的博客 3414

自动装箱和拆箱、包装类空指针异常问题、包装类的缓存问题

自动装箱和拆箱 自动装箱和拆箱就是将基本数据类型和包装类之间进行自动的互相转化。JDK1.5以后Java引入了自动装箱(autoboxing)和自动拆箱(unboxing) 自动装箱: 基本数据类型处于需要对象的环境中时,会自动转为”对象“。例如:‘ Integer i= 5;相当于编译器自动执行Integer i= Integer,valueOf(5) 自动拆箱子: 每当需要一个值时,对象会自动转成基本数据类型,没必要再去使用intvalue() 自动装箱过程时通过包装类的valueOf()方法,而自动拆

Wynb1020的博客 1651

C++类和对象的细节原理:this指针、构造函数和析构函数、深浅拷贝、运算符重载、初始化列表、类的各种成员和方法

但是在C++中,当你调用一个成员函数时,编译器会自动传递调用该函数的对象的地址作为函数的隐含参数(即 this 指针),因此你无需手动传递对象的地址。类:实体的抽象类型,比如一个人的类,就是先把人的属性、行为等抽象出来,虚虚地放在代码段上,仅仅是一个模板,类型是不占空间的。实体的属性的抽象对应类的成员变量,实体的行为对应类的成员方法。类的成员方法一经编译,所有的方法参数,都会加一个this指针,接收调用该方法的对象的地址。成员对象:B类变量是A类的成员变量之一,那么可以叫这个B类变量为A类的成员对象。

weixin_73240939的博客 1134

new和delete 基类指针指向继承类的对象时,delete的过程

释放指向派生类对象的基类指针

qq_33317320的博客 6583

HEVC中类,对象和指向对象的指针

HEVC代码中定义了几十种类,这些类的对象一般只实例化一次,在编码过程中都是通过指针来访问实例化的对象。 唯一以类名实例化的对象:cTAppEncTopmain () { TAppEncTop cTAppEncTop; }这个对象被建立时,它的所有数据成员也被建立,并实例化(占据存储空间)class TAppEncTop { TEncTop m_cTEncTop }而m_cTEnc

xidianliye的博客 756

计算机程序设计(C++)第10周编程作业数据的抽象和封装——类(2)——构造函数、析构函数和指向对象的指针

/* 1定义一个带重载构造函数的日期类 题目内容: 定义一个带重载构造函数的日期类Date,数据成员有年、月、日;成员函数包括:一个带参数的构造函数Date(int,int,int),一个不带参数的构造函数,一个按“年-月-日”格式显示日期的函数,一个对数据成员赋值的函数void init(int,int,int)。 主函数中对类的测试要求: 1. 分别使用两个不同的重载构造函数创建两个日期类对

olivialyn的博客 2841

C++中的面向对象编程:类、对象和this指针的深入理解

本文概述了C++面向对象编程的基础知识,包括类的定义、对象的创建、成员函数和变量的访问控制,以及this指针的作用。通过比较C语言的面向过程特性和C++的面向对象特性,阐述了类和对象如何简化复杂问题的解决。此外,还讨论了内存对齐规则和封装、继承、多态等OOP核心概念。

F_STQ的博客 698

C++ Primer Plus书之--C++ this指针和类作用域及作用域内枚举

this指针 this指针指向用来调用成员函数的对象(this被作为隐藏参数传递给方法), 也就是this能指向本类对象, 看一个例子, 比较两个Stock对象的total_val值, 谁的total_val值大, 就返回哪个对象. // 第一个const表示返回一个const的引用 // 第二个const表示, 要显示的访问一个const引用对象 // 第三个const表示, 函数不会修改...

c1392851600的专栏 676
上一篇: Effective C++ 2e Item40
下一篇: Effective C++ 2e Item42
lostmouse
博客等级 码龄26年 22粉丝 63原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值