一个关于私有(实现)继承的疑问,由《MEC》i.26想到的

fireseed 2006-06-20 04:37:09
《More Effective C++》第26条款实现了一个可以用来给对象计数的基类,以防止由一个类构造了超过用户设想范围的对象。有点类似于COM技术。下面是基类的定义,实现代码就不贴了,太长。

template<class BeingCounted>
class Counted
{
public:
class TooManyObjects{}; //这是个异常,当构造超过限定的对象时触发
static int objectCount() { return numObjects; } // 返回当前已构造的对象数量
Protected:
Counted(); // 构造和下面的拷贝构造的代码就是判断是否超限,如果超限就抛出上面的异常
Counted(const Counted& rhs);
private:
static int numObjects;
static const size_t maxObjects;
};

现在有一个只能同时存在有限个数对象的派生类

class Printer: private Counted<Printer>
{
public:
static Printer * makePrinter(); // 获得一个Printer指针
~Printer();
……
private:
Printer();
Printer( const Printer& rhs );
};

“……另一种做法是在 Printer 和 Counted<Printer> 之间使用公有继承,但是这么一来我们就不得不给予 Couted 类一个虚析构(否则我们就得冒险:如果有人通过 Counted<Printer>* 指针删除了一个Printer对象,会有不正确的行为)……”


上面是书上的原话,我有两点疑问。
一,Counted<Printer> 的析构是私有,那又怎么可能通过 Counted<Printer>* 的指针来删除Printer对象?将一个 Counted<Printer>* 的指针应用到 delete 操作符上应该是不合法的语句啊?

二,为何不为 Printer 实现一个 Destroy 函数来实现内存的释放,并将~Printer声明为私有?如果像上面这样子写那么下面的写法不是很难受?
Printer *prt = Printer::makePrinter();
……
delete prt;
要知道,程序员一般最害怕出现不成对的 new 和 delete 了。

请各位为我答疑,感谢。
...全文
978 22 打赏 收藏 转发到动态 举报
写回复
用AI写文章
22 条回复
切换为时间正序
请发表友善的回复…
发表回复
ybt631 2006-06-23
  • 打赏
  • 举报
回复
// testPP.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"

template<class BeingCounted>
class Counted
{
private:
static int numObjects;
static const size_t maxObjects;
public:
class TooManyObjects{}; //这是个异常,当构造超过限定的对象时触发
static int objectCount() { return numObjects; } // 返回当前已构造的对象数量
protected:
Counted(){ printf("Counted()\n");} // 构造和下面的拷贝构造的代码就是判断是否超限,如果超限就抛出上面的异常
Counted(const Counted& rhs){}

};

//现在有一个只能同时存在有限个数对象的派生类

class Printer: public Counted<Printer>
{

private:
Printer(){};
Printer( const Printer& rhs ){};
public:
static Printer * makePrinter(){ return new Printer;}// 获得一个Printer指针
~Printer(){ printf("~Printer()\n");}
};

int _tmain(int argc, _TCHAR* argv[])
{
//如果该成私有继承,这个语句将是违法的
Counted<Printer> *pObject = Printer::makePrinter();
delete pObject; //没有执行~Printer(),会存在内存泄漏。。
return 0;
}
Oversense 2006-06-22
  • 打赏
  • 举报
回复
人家是想重点讨论为什么用private继承,人家说的是假设用public,那么。。说得很末节东西。。

比如我说 假如你在月球上,你能跳很高很高,你偏偏对我说,那我在月球上要呼吸啊。。
fireseed 2006-06-22
  • 打赏
  • 举报
回复

template<class BeingCounted>
class Counted
{
public:
class TooManyObjects{}; //这是个异常,当构造超过限定的对象时触发
static int objectCount() { return numObjects; } // 返回当前已构造的对象数量
protected:
Counted(){}; // 构造和下面的拷贝构造的代码就是判断是否超限,如果超限就抛出上面的异常
Counted(const Counted& rhs){};
~Counted() { --numObjects; }
private:
static int numObjects;
static const size_t maxObjects;
};
class Printer: public Counted<Printer>
{
public:
static Printer * makePrinter(){ static Printer ptr; return &ptr; }; // 获得一个Printer指针
~Printer(){};

private:
Printer(){};
Printer( const Printer& rhs ){};
};




现在是public继承了



Counted<Printer> *x = Printer::makePrinter();
delete x; // <<-----------------这里一样会出错!
fireseed 2006-06-22
  • 打赏
  • 举报
回复
A

公有继承也不可能把Counted<Printer>的析构变成public
Oversense 2006-06-22
  • 打赏
  • 举报
回复
A.

原文是

"另一种做法是在 Printer 和 Counted<Printer> 之间使用公有继承,但是这么一来我们就不得不给予 Couted 类一个虚析构(否则我们就得冒险:如果有人通过 Counted<Printer>* 指针删除了一个Printer对象,会有不正确的行为)…… "

你的疑问是

"Counted<Printer> 的析构是私有,那又怎么可能通过 Counted<Printer>* 的指针来删除Printer对象?将一个 Counted<Printer>* 的指针应用到 delete 操作符上应该是不合法的语句啊?"


中文啊, 原文是个长句,简化后为

>假设< 使用公有继承,>那么< del ...


B.

第二点你说的部分是对的
static Printer * makePrinter(); // 获得一个Printer指针
static void buryPrinter();

这么成对写,可以避免new,del不配对

但是 现在的C++社群 >流行< 不自己写del,真的很流行啊,跟吊带装一样
流行用 boost::shared_ptr 或者 std::auto_ptr


C.

原文那个ref_ptr太老土了,现在流行 boost::shared_ptr
fireseed 2006-06-22
  • 打赏
  • 举报
回复
又扯回文字游戏了,拜托,老兄!!!!!

作者本来就是不让你直接在栈里实例化对象的,你没看贴?
howyougen 2006-06-22
  • 打赏
  • 举报
回复
“……另一种做法是在 Printer 和 Counted<Printer> 之间使用公有继承,但是这么一来我们就不得不给予 Couted 类一个虚析构(否则我们就得冒险:如果有人通过 Counted<Printer>* 指针删除了一个Printer对象,会有不正确的行为)……”

注意这里meryes说的是如果
如果Counted有个友元呢?

我想meryes主要是想说,如果一个类用作基类,那么就要使析构函数为虚函数
howyougen 2006-06-22
  • 打赏
  • 举报
回复
并将~Printer声明为私有?

设成私有,还可能实例化么?

class Cat
{
~Cat();
}

你试着实例化Cat看什么结果。
lddLinan 2006-06-22
  • 打赏
  • 举报
回复
我不清楚标准在这方面是怎么规定的,不过有100%符合标准的编译器么?而且你能确定作者完成那本书的时候,编译器满足多少的规范么?

不过我实在想不出来如何直接delete Counted<Printer>*
不知道下面的情况算不算:
class AccessChanger : Counted<printer>
{
public:
~AccessChanger();
}
Printer* tPrinter;
...

AccessChanger* tAC = (AccessChanger*)tPrinter;
delete tAC;

如果Counted<printer>的析构函数为virtual那么上面的强制转换依然会调用Printer的析构函数
fireseed 2006-06-22
  • 打赏
  • 举报
回复
Oversense
你是不懂C++还是没仔细看我的问题和回复?

to lddLinan
这贴子和MEC所讨论的东西都是基于ISO C++,不考虑标准以外的东西。
如果你能在一个标准的C++编译器上,不改变Counted<Printer>而做这样的调用:
Counted<printer> *a;
delete a;
我就彻底服你了。


你们的回答都没有解决问题。到底如何“通过 Counted<Printer>* 指针删除了一个Printer对象”,你们不要说“人家是假设的某环境,你不要较真”这类的话。

我现在就是需要知道这个能让面的代码运行的假设的环境倒底是怎么回事!

lddLinan 2006-06-22
  • 打赏
  • 举报
回复
这取决于编译器如何理解虚拟函数的调用。
虚函数的访问权限很难控制,编译器能做的只是尽可能的保证它所能看到的访问权限限制,目前编译器都无法保证通过基类虚函数指针访问子类private重载的虚函数。

如果一个简单的编译器,他遇到虚函数调用时只是简单的作指针偏移
这样的话,虚函数实际上不受任何private或是protected的限制
比如 Counted<printer> *a;
delete a;
被翻译为a._vptr[1](a),这时候编译器不知道a._vptr[1]的存取权限,当然可以调用

其实这样的策略比较直观,编译器不知道基类指针到底指向了哪个类型,那么到底应该使用哪个类的存取限制呢?有些编译器因为使用了一些策略,以指针的静态类型存取权限为准(VC6.0)。



“否则我们就得冒险:如果有人通过 Counted<Printer>* 指针删除了一个Printer对象,会有不正确的行为)……””
==============================================================
这句话是虚析构函数的基本概念:如果你要通过基类的指针删除子类,那么必须将基类的析构函数设为虚以保证子类的析构函数得到调用。(这就是那个”否则“的意思)


fireseed 2006-06-20
  • 打赏
  • 举报
回复
也许会说:"Gee, you've found a sure slip in my works. Ok, i'll order my Public Foundation to reward you 10 million dollars for my promiss. give me your post address please."
sharpdew 2006-06-20
  • 打赏
  • 举报
回复
他也许会说,"Sometimes we may let the dctor of Counted<Printer> public, after all, that is OK! "
fireseed 2006-06-20
  • 打赏
  • 举报
回复
汗,给mayers写封信吧
sharpdew 2006-06-20
  • 打赏
  • 举报
回复
“Otherwise we'd risk incorrect behavior if somebody deleted a Printer object through a Counted<Printer>* pointer”
看来只能当这句话没说!
fireseed 2006-06-20
  • 打赏
  • 举报
回复
是,书上也是这么讲的,但我搞不清的是括号里的那句话
fireseed 2006-06-20
  • 打赏
  • 举报
回复
你是说下面这样吗?


class Test : public Counted<Derived>
{
void func()
{
Counted<Printer> *p = Printer::makePrinter();
delete p;
}
};

首先p就不能被赋值,VC的编译器给出下面警告
/////////////////////////////////////////////
错误消息
从“type1”到“type2”的“conversion type”转换存在,但不可访问

访问保护(protected 或 private)阻止从指向派生类的指针到指向基类的指针的转换。

下面的示例生成 C2243:

复制代码
// C2243.cpp
// compile with: /c
class B {};
class D : private B {};
class E : public B {};

D d;
B *p = &d; // C2243

E e;
B *p2 = &e;

/////////////////////////////////////////////////////

其次,delete p也不允许,因为~Base必竟是Base的保护成员,除了自己派生的那一系,外部或旁系都无权调用
sharpdew 2006-06-20
  • 打赏
  • 举报
回复
我觉得这里使用private继承而不是public继承,只是设计上更漂亮而已,因为根本不需要用户直接实例化Counted这个基类,实际上也没办法使用,那么Counted就以“用...来实现”的语义给别的类继承使用,所以采用private再恰当不过了。
sharpdew 2006-06-20
  • 打赏
  • 举报
回复
protected的话,如果在其继承类中通过 Counted<Printer>* 的指针来删除Printer对象呢?
fireseed 2006-06-20
  • 打赏
  • 举报
回复
主要就是对这一句不明白:“如果有人通过 Counted<Printer>* 指针删除了一个Printer对象,会有不正确的行为”

见《MEC》中文版第二版 P143
加载更多回复(2)

64,649

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧