日历
网志分类
· 所有网志 (92)
· C++ (37)
· Algorithm (25)
· Web Dev (0)
· Design Pattern (0)
· STL (0)
· bbs search project (2)
· Paper (3)
· English (0)
· Career Dev (7)
· 未分类 (18)
最新的评论
站内搜索
友情链接
· 我的歪酷 非非共享界
· jammy 生活就是映像
· Aleph mm's
· onebird(一鸣)
· lucene.com.cn
· Goolge黑板报--没事看看
· jjing

订阅 RSS

0051728

歪酷博客

kua's

海明威说--
“这个世界是美好的,值得我们为之奋斗”

我相信后半句...

                                                                                    kuapig@gmail.com


kua @ 2009-12-07 16:38

The Union Operation
To find all customers having a loan, an account, or both at the bank, we write
(
select customer-name from depositor) union (select customer-name from borrower)
The union operation automatically eliminates duplicates, unlike the select clause.
Thus, in the preceding query, if a customer—say, Jones—has several accounts or
loans (or both) at the bank, then Jones will appear only once in the result.
If we want to retain all duplicates, we must write
union all in place of union:
(
select customer-name from depositorunion all  (select customer-name from borrower)
The number of duplicate tuples in the result is equal to the total number of duplicates
that appear in both
d and b. Thus, if Jones has three accounts and two loans at the
bank, then there will be five tuples with the name Jones in the result.

The Intersect Operation
To find all customers who have both a loan and an account at the bank, we write
(
select distinct customer-name from depositor) intersect
(select distinct customer-name from borrower)
The
intersect operation automatically eliminates duplicates. Thus, in the preceding
query, if a customer—say, Jones—has several accounts and loans at the bank, then
Jones will appear only once in the result.
If we want to retain all duplicates, we must write
intersect all in place of intersect:
(
select customer-name from depositor) intersect all (select customer-name from borrower)
The number of duplicate tuples that appear in the result is equal to the minimum
number of duplicates in both
d and b. Thus, if Jones has three accounts and two loans
at the bank, then there will be two tuples with the name Jones in the result.To find all customers who have an account but no loan at the bank, we write
(select distinct customer-name from depositor) except (select customer-name from borrower)
The
except operation automatically eliminates duplicates. Thus, in the preceding
query, a tuple with customer name Jones will appear (exactly once) in the result only
if Jones has an account at the bank, but has no loan at the bank.
If we want to retain all duplicates, we must write
except all in place of except:
(
select customer-name from depositor) except all (select customer-nam from borrower)
 

The Except Operation

To find all customers having a loan, an account, or both at the bank, we write
(
select customer-name from depositor) union (select customer-name from borrower)
The union operation automatically eliminates duplicates, unlike the select clause.
Thus, in the preceding query, if a customer—say, Jones—has several accounts or
loans (or both) at the bank, then Jones will appear only once in the result.
If we want to retain all duplicates, we must write
union all in place of union:
(
select customer-name from depositor) union all (select customer-name from borrower)



 
kua @ 2009-01-05 15:21

一, 为什么会有字节对齐问题?
字节对齐问题之主观所以存在,我想是源于程序员和CPU对内存数据访问的理解稍有不同。在我们看来,程序对内存数据的访问总是按照其大小,一个字节接着一个字节进行的,比如读取一个char型变量,系统就读去一个字节的内容,读取一个int型变量,系统就读取四个字节的内容:
0|1|2|3|4|5|6|7……
但是事实并非如此。并非所有CPU都是一个一个字节地来访问内存的(现在可能很少有CPU这么做,效率太低了!)。比如,CPU可以按照1,2,4,6, 8,16,32……的粒度来访问内存数据。这被称之为memory access granularity(内存访问粒度/间隔)[1],例如按照4字节来访问内存:
0123|4567|8……
显然,内存访问效率和数据对象的大小以及CPU的内存访问粒度相关。比如我们在32位系统上访问一个4字节int型数据对象,假设这个对象a的起始地址为0×00:
0×00 0×01 0×02 0×03
这时候,a的内存位置处于效率最佳情况下:
访问粒度 CPU访问次数
1字节 4次
2字节 2次
4字节 1次
我们当然希望每次运行程序,支持4字节访问的CPU都可以一次就读/写这个int型数据。但是事实确未必一定是这样。如果a的起始地址为0×01,则:
访问粒度 CPU访问次数
1字节 4次
2字节 3次
4字节 2次
按照2字节访问需要三次:第一次访问0×00~0×01,第二次访问0×02~0×03,第三次访问0×04~0×05,然后把三部分数据拼凑起来。
同理,按照4字节访问需要两次:第一次访问0×00~0×03,第二次访问0×04~0×07,然后把两部分数据拼凑起来。
显然,a的地址不符合一定的要求,就会导致效率损失。
二,X86的字节对齐规则
Kang Su Gatlin在其文章[2]中指出了X86上对齐的原则。32位X86CPU支持1,2,4,8字节的访问。访问L字节(2的整数倍)数据,其地址A满足A MOD L = 0。
这样对基本的数据类型就有如下规则:
char —— 单字节对齐,A MOD 1 = 0;可为任意的奇/偶值
short —— 双字节对齐,A MOD 2 = 0;偶地址
int —— 四字节对齐,A MOD 4 = 0;地址最后一位为:4,8,C
double —— 八字节对齐, A MOD 8 = 0;地址最后一位为:0,8
三,堆栈的对齐布局
先用VC6运行一个例子:
#include
int main(int argc, char* argv[])
{
char c0;
char c1;
char c2;
printf(”%p\n%p\n%p\n”,&c0,&c1,&c2);
return 0;
}
在Debug模式下,得到类似如下的结果(具体地址值可能不同):
0012FF7C
0012FF78
0012FF74
OK,这个结果的每个地址,都符合1字节对齐,MOD 1 = 0。不过,等等,也符合四字节对齐阿!MOD 4 = 0。堆栈理论上只需要3个字节,这里却用了12个字节。
因为VC6编译器默认对堆栈采用了4字节对齐原则。所以在不进行任何优化的情况下,一个char对象也需要占据4个字节空间。这样做的好处呢?—— 显然,无论如何,1字节和2字节对� B68 ��都是符合对齐原则的。
用RELEASE模式,把编译选项调整到MAXSPEED或者MAXSIZE,可以得到如下的输出:
0012FF7D
0012FF7E
0012FF7F
这次堆栈中就没有冗余的字节了。访问速度也不会因为字节排列而下降。
对于32位总线而言,我认为8字节对齐可能没有什么太大意义。因为无论如何,8字节数据对象,例如double,都需要两个时钟周期的。这也就是为什么 VC6在32位平台上总是默认4字节对齐的原因:4字节对齐总是兼容单/双字节对齐方式的。关于32位总线上8字节对齐的性能影响,我么在后面的例子中可以看到。
我们可以修改上一个例子:
#include
int main(int argc, char* argv[])
{
char c0;
char c1;
double d0;
char c2;
printf(”%p\n%p\n%p\n%p\n”,&c0,&d0,&c1,&c2);
return 0;
}
Debug模式下:
0012FF7C
0012FF6C
0012FF78
0012FF74
VC6将堆栈变量的位置做了偏移。double被放在了栈顶。这样做的好处是什么暂时不太清楚。
四,结构体/联合体的布局
Kang Su Gatlin指出了struct/union的布局规则,对于inter-struction/inter-union,对齐规则很简单,即 struct/union中最大的那个alignment,而这个max(alignment),又取决于编译器的设置(用参数/Zpn或者代码中用 #pragma pack(n)),即自身的对齐要求和编译器设置对齐要求二者中的最小值。
4.1 简单的情况
例如:
#include “stdio.h”
struct SA
{
char g;
double k;
};

int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d\nAddr=%p\n”,sizeof(a),&a);
return 0;
}

输出结果如下:
Size=16
Addr=0012FF70
VC6默认采用8字节对齐,double的对齐要求也是8字节。因此这里输出大小为16,地址按照规则需符合8字节对齐,最后四位为0000。
我们可以加上pack 限制
#include “stdio.h”
#pragma pack(4)
struct SA
{
char g;
double k;
};

int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d\nAddr=%p\n”,sizeof(a),&a);
return 0;
}
则输出为:
Size=12
Addr=0012FF74
将默认的对齐边界设置为4,则成员g仅占用4个字节,因为k在4字节边界上对齐。由于SA的成员最大对齐值为4,所以整个结构体也4字节对齐。所以地址的最后4位可以被4整除。
4.2 嵌套的结构体成员
看一个例子:
#include “stdio.h”
struct SB
{
int c;
double k;
short b;
};

struct SA
{
char g;
SB b;
char k;
};

int main(int argc, char* 1498 argv[])
{
SA a;
printf(”Size=%d\nAddr=%p\n”,sizeof(a),&a);
return 0;
}
输出为:
Size=40
Addr=0012FF58
从这个例子可以看出嵌套结构体的规则其实也很简单。首先找到SA中成员的最大对齐边界,由于b是结构体类型,因此b的对齐边界取决于SB中的成员最大对齐边界:8。因此,SA中的成员8字节对齐。由于SB中的成员也是8字节对齐的,所以大小为8*5=40。而整个变量a的地址也要符合8字节对齐要求。
再看看用了pack(4)的情况:
#include “stdio.h”
#pragma pack(4)
struct SB
{
int c;
double k;
short b;
};

struct SA
{
char g;
SB b;
char k;
};

int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d\nAddr=%p\n”,sizeof(a),&a);
return 0;
}
按照分析,SB的最大对齐边界:4,所以SA的对齐边界也是4,这个结果应该输出:
Size=24
Addr=0012FF68
注意pack(n)的限制,作用域从起始位置开始。例如:
#include “stdio.h”
struct SB
{
int c;
double k;
short b;
};

#pragma pack(4)
struct SA
{
char g;
SB b;
char k;
};

int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d\nAddr=%p\n”,sizeof(a),&a);
return 0;
}
SB按照8字节对齐,SA按照4字节对齐,其成员b整体上满足4字节对齐即可。因此大小是8*3(SB大小)+8(SA的成员g和k的大小):
Size=32
Addr=0012FF60
再看更复杂的例子:
#include “stdio.h”
#pragma pack(4)
struct SB
{
int c;
double k;
short b;
};

#pragma pack(2)
struct SA
{
char g;
SB b;
char k;
};

int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d\nAddr=%p\n”,sizeof(a),&a);
return 0;
}
SB的对齐边界为4,因此大小为16,SA的对齐边界为2(最小值),因此大小为16+4=20:
Size=20
Addr=0012FF6C
再来看看成员变量位置的颠倒会给struct带来什么结果?
#include “stdio.h”
struct SB
{
int c;
short b;
double k;
};
struct SA
{
char g;
SB b;
char k;
};
int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d\nAddr=%p\n”,sizeof(a),&a);
return 0;
}
运行结果如下:
Size=32
Addr=0012FF60
变量a的大小变成了32,因为SB中的double成员k的声明被放在了最后。而int成员c和short成员b各占4字节,就可以保证k处于8字节对齐状态。因此,SB的大小为16。再加上SA的另外两个成员:g和k分别占用8个字节,所以得到的Size就为32。同样,地址还是符合8字节对齐边界的要求。
从这个例子也可以看出,通过合理安排结构体成员的声明顺序,可以减少其占用内存的大小。
五,性能
我使用了如下的程序来测试自己对齐给性能带来的影响:
// Test.cpp : Test the performance of data alignment of 1, 2, 4, 8
#include “stdafx.h”
#include
#include
#include
#include

LARGE_INTEGER operator -(LARGE_INTEGER a, LARGE_INTEGER b)
{
LARGE_INTEGER reVal;
if (a.LowPart
class CCountTest
{
public:
CCountTest()
{
dest = new T[128];
origsource = new byte[10000];
}
~CCountTest()
{
delete[] origsource;
delete[] dest;
}

void CountTest(int offset)
{
UINT iters = 99999;
T* source;
LARGE_INTEGER startCount, endCount, freq;

byte* pByte = GetAlign(origsource, offset);
source = (T *) (pByte);
printf(”dest = %p source = %p\n”, dest, source);

QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&startCount);
{
for (UINT x = 0; x CountTest;
CountTest.CountTest(1);
CountTest.CountTest(2);
CountTest.CountTest(4);
CountTest.CountTest(8);
return 0;
}
这里我们用double类型进行测试,从一个数组中向另一个数组复制数据,测试环境:
Intel P4 2.8G + 512 DDR + Windows2000
输出结果如下:
dest = 00342080 source = 00342489
elapsed time = 0;111243
dest = 00342080 source = 0034248A
elapsed time = 0;111481
dest = 00342080 source = 0034248C
elapsed time = 0;74270
dest = 00342080 source = 00342488
elapsed time = 0;69388

可以看到,1字节和2字节对齐是,对double数据的存取远比4或者8字节对齐来的低效!但是4字节和8字节对齐似乎没有对double的存取造成多大的效率差异。实事上,实际运行中最后两个时间值得大小关系并不唯一确定,这里只是一次运行的结果。这可能说明,在32位平台上,double类型数据对象的 8字节对齐,其实并没有比4字 1768 节对齐来的效率更高。毕竟32位总线不可能一次传输完8个字节的数据。这也符合前面我们提到的栈字节对齐中为何double 型变量并不一定被系统安排为8字节对齐的情况。

六,尾巴
个人觉得,其实大多数情况下,在Windows上开发,我们都不用关心堆栈和结构体的成员声明顺序,以及是否通过填充冗余字节来达到地址对齐。因为编译器通过优化选项可以自动进行处理。即使浪费了一些内存,在大多数应用中,恐怕也无关痛痒。只是不要相当然的用一个常数来替代sizeof函数就好。
另外就是程序移植到其他的平台上时,可能目标平台并不支持非对齐式的数据访问。编译器也不会自动纠正对齐问题。这样一来,如果想编写移植性好的程序,就要非常小心了。

参考文章:

1,Jonathan Rentzsch : Data alignment: Straighten up and fly right (http://www-128.ibm.com/developerworks/library/pa-dalign/ )

2,Kang Su Gatlin :Windows Data Alignment on IPF, x86, and x64 (http://msdn.microsoft.com/library/en-us/dv_vstechart/html/vcconwindowsdataalignmentonipfx86×86-64.asp?frame=true)

续:

MSN的C++ Group里看到一些讨论,关于栈变量放置的问题:

我们可以修改上一个例子:
#include
int main(int argc, char* argv[])
{
char c0;
char c1;
double d0;
char c2;
printf(”%p\n%p\n%p\n%p\n”,&c0,&d0,&c1,&c2);
return 0;
}
Debug模式下:
0012FF7C
0012FF6C
0012FF78
0012FF74
VC6将堆栈变量的位置做了偏移。double被放在了栈顶。这样做的好处是什么暂时不太清楚。
——–double放在栈顶,因为有些编译器可以将同一类型的变量连续放置(好处仍然不清楚)。如果要强行安排这些变量的放置顺序,可以用一个struct:

struct A{

char c0;
char c1;
double d0;
char c2;

}

这样就OK了,当然前提是不能让编译器优化struct内部的放置顺序。




 
kua @ 2008-12-24 14:34

Please look at the code below, any problem you find?

 

p += 1;

 

int* p = new int[5];delete [] p; //or delete p;

When you apply for a memory space, the system will record this space, it will have a head and tail to tag this space.
The only way to free this space is to delete or free the head. Otherwise you will meet the expression:_CrtIsValidHeapPointer(pUserData)  error.


 
kua @ 2007-11-16 19:11

当你需要处理XML文档时,你的首要选择是使用DOM(文档对象模型)还是使用SAX(用于XML的简单API),即当前使用的两个主要的XML API。你可以使用任何一种(或者在同一时间使用两种)来处理XML文档,然而DOM将文档载入到内存中处理,而SAX则相反,它可以检测一个即将到来的 XML流,由此并不需要所有的XML代码同时载入到内存中。
选择DOM与SAX,与在一个数据库中的表单与视图之前选择一样:选择适合于当前实际情况的方法。如果你只是想简单地查看XML文档而不处理它,那么请选择使用SAX。


       SAX与DOM之间的区别


SAX与DOM之间有一些显著区别,包括:
         DOM是复杂对象处理的首选,比如当XML比较复杂的时候,或者当你需要随机处理文档中数据的时候。SAX从文档的开始通过每一节点移动,以定位一个特定的节点。
DOM为载入到内存的文档节点建立类型描述。最终,这些描述呈现了可容易横向移动、潜在巨大、树型结构。如果XML很冗长,DOM就会显示出无法控制的胀大。例如,一个300KB的XML文档可以导致RAM或者虚拟内存中的3,000,000KB的DOM树型结构。通过比较就会发现,一个SAX文档根本就没有被解构,它也没有隐藏在内存空间中(当然当XML流被读入时,会有部分文档暂时隐藏在内存中)。SAX就是一种“更轻巧的”技术──它可以给你的系统带来更轻的负担。SAX相当于观看一场马拉松比赛,而DOM就好比邀请所有的比赛选手到家里参加晚餐。
所以,你如何选择SAX和DOM?如果你处理复杂的东西,比如高级XSLT转换,或者Xpath过滤,请选择使用DOM。如果你建立或者更改XML文档,你也可以选择DOM。
相反,你可以使用SAX来查询或者阅读XML文档。SAX可以快速扫描一个大型的XML文档,当它找到查询标准时就会立即停止,然后再处理之。
在某些情况下,在一个方案中,最佳的选择是使用DOM和SAX处理不同的部分。例如,你可以使用DOM将XML载入到内存并改变它,然后通过从DOM树中发送一个SAX流而转移最后的结果。


SAX概念

        SAX是Simple API for XML的缩写,它并不是由W3C官方所提出的标准,可以说是“民间”的事实标准。实际上,它是一种社区性质的讨论产物。虽然如此,在XML中对SAX的应用丝毫不比DOM少,几乎所有的XML解析器都会支持它。

与DOM比较而言,SAX是一种轻量型的方法。我们知道,在处理DOM的时候,我们需要读入整个的XML文档,然后在内存中创建DOM树,生成DOM树上的每个Node对象。当文档比较小的时候,这不会造成什么问题,但是一旦文档大起来,处理DOM就会变得相当费时费力。特别是其对于内存的需求,也将是成倍的增长,以至于在某些应用中使用DOM是一件很不划算的事(比如在applet中)。这时候,一个较好的替代解决方法就是SAX。

        SAX在概念上与DOM完全不同。首先,不同于DOM的文档驱动,它是事件驱动的,也就是说,它并不需要读入整个文档,而文档的读入过程也就是SAX的解析过程。所谓事件驱动,是指一种基于回调(callback)机制的程序运行方法。(如果你对Java新的代理事件模型比较清楚的话,就会很容易理解这种机制了)

        在XMLReader接受XML文档,在读入XML文档的过程中就进行解析,也就是说读入文档的过程和解析的过程是同时进行的,这和DOM区别很大。解析开始之前,需要向XMLReader注册一个ContentHandler,也就是相当于一个事件监听器,在ContentHandler中定义了很多方法,比如startDocument(),它定制了当在解析过程中,遇到文档开始时应该处理的事情。当XMLReader读到合适的内容,就会抛出相应的事件,并把这个事件的处理权代理给ContentHandler,调用其相应的方法进行响应




 
kua @ 2007-11-10 18:26




什么是析构函数
------------------
class A
{
public:
A();
~A(); // 析构函数 : 在类对象生命周期结束时被自动调用,来释放一些资源;
};

另外:基类析构函数声明为virtual是很重要的~

比如:有一class B 继承自 上边的 class A;
如果我们这样 A pClassA = new B(pClassA这里是个指针?); 即new 一个派生类(B)的对象,并且有 基类的指针来指向它~这样没问题,但是 如果我们要释放刚才的空间~ delete pClassA 这个就会有麻烦,因为C++标准中对这一行为没有明确定义,即通过基类指针来销毁派生类对象这个行为的结果是无法预期的,其中可能直接导致,delete pClassA 这个行为 根本不能使 派生类的(B)的析构函数被调用到,如果B的析构函数中有一些 必须要做的事 (比如释放资源什么的)那么将无法进行了~;

如果将 基类(A)的析构函数声明为virtual就不会这样了,这是因为虚函数会由类的虚函数表(vtalbe)来维护,每当通过指针或引用来调用虚函数,就会到这个vtable中去找相应的虚函数指针;

每个有virtual函数的类都有一份vtable,派生类将自动继承基类的vtable,vtable中实际存储的是virtual函数的指针~
每当派生类改写了基类虚函数,vtable的相应指针项就会更改了~而指向新的(派生类override的)虚函数地址~

这样 我们上边的delete pClassA这个行为 就会引发 B类的析构函数了,



上面介绍的有关虚函数表的概念 也真是 C++ 类的多态行为的内幕原理~


 
kua @ 2007-10-31 17:58

函数内无任何判断,内存分配以及2个指针指向的内存不交叠判断都由用户判断
char* mystrcpy(char* dest, const char* src)
{
 char* p = des;
 while(*p++ = *src++);
  return dest;
}


 
kua @ 2007-10-31 12:48

Select  [Distinct()] From 
Where
Group by  Having
Order by 


Select  count(1), date From
Where
Group by date Having count(*) > 3
Order by date

group by
分组
通用数据库具有基于表的特定列对数据进行分析的能力。
可按照在 GROUP BY 子句中定义的组对行进行分组。以其最简单的形式,组由称为分组列的列组成。 SELECT 子句中的列名必须为分组列或列函数。列函数对于 GROUP BY 子句定义的每个组各返回一个结果。下列示例产生一个列出每个部门编号的最高薪水的结果:
SELECT DEPT, MAX(SALARY) AS MAXIMUM
FROM STAFF
GROUP BY DEPT


 
kua @ 2007-10-29 15:37

以下都是我个人的理解,估计有问题,看的时候请慎重考虑,仔细判断
1.被虚继承的类就是虚基类
2.何为虚继承: 先说普通继承
普通继承: class B: public A, 
B从A继承,B会将A做一份copy,作为自身的一部分,然后在加自己独有的一部分。
加入A是一个有虚函数的类,考虑A和B的结构
sizeof(A) = sizeof(A的数据成员) + sizeof(vptr),这个vptr指向A类的 vtable
sizeof(B) = sizof(A的数据成员) + sizeof(B数据成员) + sizeof(vptr),这个vptr指向B类的vtable

虚继承: 虚继承与普通继承不同点在于,子类不实际的copy父类的一部分,而是生成一个引用,指向父类对象
但是在用sizeof统计大小时,依然将父类的成员变量统计在内,此时B的结构如下:
sizeof(B) = sizof(A的数据成员) + sizeof(B数据成员) + sizeof(vptr) + sizeof(pointer to A), vptr指向B类的vtable, 并且多了一个引用指向A对象

虚继承的这个特性在C++的多重继承应用中可以起到很好的作用:
一个没有虚继承的多重继承:
class A
{
 void f() ;
};
class B: public A
{
};
class C:public A
{
}
class D: public B, public C
{
}
void main()
{
 D d;
d.f();
}
这种情况下编译器会报错:D::f is ambiguous,could be the 'f' in base 'A' of base 'B' of class 'D',or the 'f' in base 'A' of base 'C' of class 'D'
很明显看出:d.f()是有歧义的,因为D中有2个A的copy,也就有2个A::f()函数,编译器不知道应该调用哪一个。

这个问题应用虚继承可以得到很好的解决,类定义重写如下:
class A
{
 void f() ;
};
class B: public virtual A
{
};
class C:public virtual A
{
}
class D: public B, public C
{
}
void main()
{
 D d;
d.f();
}
此时D中只指向一个A对象,调用d.f(),就是调用那个唯一的A的f()函数,没有歧义,顺利执行。因为此时B和C都是指向同一个A对象。
此时D的结构如下图:

需要注意的是B和C都必须是从A虚继承,只有一个是虚继承仍然会有歧义



 
kua @ 2007-10-29 13:11

C++中是不允许virtual 构造函数的,如果在构造函数前加virtual
以vc6为例,编译器报错为:'inline' is the only legal storage class for constructors
虽然不明白这个跟inline有什么关系,但是错误是肯定的

构造函数和析构函数都可以是private的,这种情况下编译通过,但是该类无法被实例化,即使继承也不行
例如:

class A       
{
private:
 A();
 virtual ~A();

};

class B : public A 
{
public:
 B();
 virtual ~B();

};
B b;
以上代码是编译不通过的,因为B() , ~B()分别需要调用A(),~A(),而它们都是private的 所以无法调用

解决方法只有friend
包括friend class和 friend function
例如可以将A的定义修改如下,就可以实现

class A 
{
private:
 A();
 virtual ~A();
 friend class B;

};

或者friend函数也可以在内部生成A的变量,因为它可以调用A的私有构造函数和析构函数