1、为什么C++中不允许类的大小是0

  1. class ZeroSizeT {};
  2. ZeroSizeT z[10];
  3. &z[i] - &z[j];
  4.  
  5. 一般是用两个地址之间的字节数除以类型大小而得到的,而类型大小是0将会出问题

2、为什么有时多个类组成实例所占空间都是一样的

  1. class Empty
  2. { };
  3.  
  4. class EmptyToo : public Empty
  5. { };
  6.  
  7. class EmptyThree : public EmptyToo
  8. { };
  9.  
  10. sizeof(Empty) : 1
  11. sizeof(EmptyToo) : 1
  12. sizeof(EmptyThree) : 1

空基类优化:只要不会与同一类型的另一个对象或子对象分配在同一地址,就不需要为其分配空间

1、为什么C++中不允许类的大小是0

  1. class ZeroSizeT {};
  2. ZeroSizeT z[10];
  3. &z[i] - &z[j];
  4.  
  5. 一般是用两个地址之间的字节数除以类型大小而得到的,而类型大小是0将会出问题

2、为什么有时多个类组成实例所占空间都是一样的

  1. class Empty
  2. { };
  3.  
  4. class EmptyToo : public Empty
  5. { };
  6.  
  7. class EmptyThree : public EmptyToo
  8. { };
  9.  
  10. sizeof(Empty) : 1
  11. sizeof(EmptyToo) : 1
  12. sizeof(EmptyThree) : 1

空基类优化:只要不会与同一类型的另一个对象或子对象分配在同一地址,就不需要为其分配空间。

3、对于空基类优化,如何理解前提条件“只要不会与同一类型的另一个对象或子对象分配在同一地址”,以及why

  1. class Empty
  2. { };
  3.  
  4. class EmptyToo : public Empty
  5. { };
  6.  
  7. class EmptyThree : public Empty, public EmptyToo
  8. { };
  9.  
  10. sizeof(Empty) : 1
  11. sizeof(EmptyToo) : 1
  12. sizeof(EmptyThree) : 2

NoEmpty的基类Empty和EmptyToo不能分配在同一地址空间,C++内存布局不允许相同类型的子对象偏移量相同。

对空基类优化进行限制的根本原因在于,我们需要能比较两个指针是否指向同一对象。由于指针几乎总是用地址作内部表示,因此必须保证两个不同的地址对应两个不同的对象。

更详细的:http://www.programlife.net/the-empty-base-class-optimization.html

附:

关于空基类优化的应用:在《C++ Templates The Complete Guide》e_CN的p_435中关于函数对象的组合问题,使用空基类优化的特性,将组合关系改成了继承关系。


What is empty class and why worth optimization

Empty class顾名思义就是空类,比如

  1. class empty {};

这里empty显然是一个空类,什么成员都没有。但是空类不限于这种形式,对于只有成员函数,并且没有non-static data member的类,也可以是空类。

  1. class empty
  2. {
  3. public:
  4. static void f();
  5. void f();
  6. };
  7. class empty_too : public empty {};
  1. 但是有一点需要注意,如果一个类或者他的基类中包含虚函数,那么该类就不是empty class,因为通常一个含有虚函数的类,都有一个vptr,所以就有了数据成员,虽然是隐藏的。 

对于父类是虚基类的情况也是一样,因为一般子类需要一个指针指向虚基类的子对象。

对于一个空类,它的大小不是0,因为如果是0,那么两个对象就会拥有相同的地址,这样就无法简单用地址区分两个对象了。通常一个空类的大小可能是1,也可能是4,这取决于编译器。

如果一个类,它包含空类成员子对象,那么这就会造成一定的空间浪费,而这个浪费是可以避免的,因为有些编译器实现了一种称为empty base class optimization的优化。

标准中提到了这种优化:

10/5 Derived classes

A base class subobject may be of zero size (clause 9); however, two subobjects that have the same class type and that belong to the same most derived object must not be allocated at the same address (5.10).

基类子对象可以是大小为0的,但是限制是,两个相同类型的子对象不能分配在相同的地址上。所以技巧就是通过从空类来继承是实现一定的优化。

  1. class empty1 {};
  2. class empty2 {};
  3. class non_empty1
  4. {
  5. public:
  6. empty1 o1;
  7. empty2 o2;
  8. };
  9. class non_empty2
  10. : public empty1
  11. , public empty2
  12. {};
  1. 这里empty1,2是空类,通过继承,non_empty2的大小还是1。但是non_empty1的大小就是2

需要注意的是,继承可能会带来对接口的影响,因为在泛型代码中,你不知道用户传入的类是否包含虚函数,如果包含虚函数,那么可能有一个与我们的类正好同名的虚函数,这样我们的函数就被虚化了。 
解决这个问题可以不直接从空类继承,而是创建一个中间类,并让这种类来继承空类,这样可以将影响限制在我们的中间类中。并将这个中间类的对象作为成员保存。

  1. class empty {};
  2. class foo : public empty {}; // not always correct
  3. class foo
  4. {
  5. class bar : public empty {};
  6. // ok, the interface of foo is not affected by the inheritance from empty;
  7. };
  1. stl中,大量用到了函数对象,并且有许多函数对象是空的,如果大量存储这些函数对象也是会造成一定的浪费的(为什么要存储?假设一下,哈哈)。

在《C++ template metaprogramming》中有这样一个例子:有一个类,实现一个简单的复合函数f(g(x))

  1. template<typename R, typename F, typename G>
  2. class composed_fg
  3. {
  4. public:
  5. composed_fg(const F& f, const G& g)
  6. : f(f)
  7. , g(g)
  8. {}
  9. template<typename T>
  10. R operator ()(const T& t) const
  11. {
  12. return f(g(t));
  13. }
  14. private:
  15. F f;
  16. G g;
  17. };
  1. 这里如果f或者g是空类,那么就会造成空间的浪费,视编译器而定,composed_fg最多可能会在32-bit平台上占用8字节。但是我们进行空基类优化,当f或者g中有空基类时,我们选择不同的实现。 

boost.compressed_pair就实现了一个优化过的std.pair,我们来分析一下boost.compressed_pair的实现。

compressed_pair根据T1, T2的类型,来选择不同的实现,有6种情况

T1 == T2 T1 empty T2 empty
false false false
false true false
false false true
false true true
true false false
true true true

其中区分T1==T2是因为,C++不允许有2个相同的直接基类。

  1. What is empty class and why worth optimization
  2. Empty class顾名思义就是空类,比如
  3. [cpp] view plaincopyprint?
  4. class empty {};
  5. 这里empty显然是一个空类,什么成员都没有。但是空类不限于这种形式,对于只有成员函数,并且没有non-static data member的类,也可以是空类。
  6. [cpp] view plaincopyprint?
  7. class empty
  8. {
  9. public:
  10. static void f();
  11. void f();
  12. };
  13.  
  14. class empty_too : public empty {};
  15. 但是有一点需要注意,如果一个类或者他的基类中包含虚函数,那么该类就不是empty class,因为通常一个含有虚函数的类,都有一个vptr,所以就有了数据成员,虽然是隐藏的。
  16. 对于父类是虚基类的情况也是一样,因为一般子类需要一个指针指向虚基类的子对象。
  17. 对于一个空类,它的大小不是0,因为如果是0,那么两个对象就会拥有相同的地址,这样就无法简单用地址区分两个对象了。通常一个空类的大小可能是1,也可能是4,这取决于编译器。
  18. 如果一个类,它包含空类成员子对象,那么这就会造成一定的空间浪费,而这个浪费是可以避免的,因为有些编译器实现了一种称为empty base class optimization的优化。
  19. 标准中提到了这种优化:
  20. / Derived classes
  21. A base class subobject may be of zero size (clause ); however, two subobjects that have the same class type and that belong to the same most derived object must not be allocated at the same address (5.10).
  22. 基类子对象可以是大小为0的,但是限制是,两个相同类型的子对象不能分配在相同的地址上。所以技巧就是通过从空类来继承是实现一定的优化。
  23. [cpp] view plaincopyprint?
  24. class empty1 {};
  25. class empty2 {};
  26.  
  27. class non_empty1
  28. {
  29. public:
  30. empty1 o1;
  31. empty2 o2;
  32. };
  33.  
  34. class non_empty2
  35. : public empty1
  36. , public empty2
  37. {};
  38. 这里empty1,2是空类,通过继承,non_empty2的大小还是1。但是non_empty1的大小就是2
  39. 需要注意的是,继承可能会带来对接口的影响,因为在泛型代码中,你不知道用户传入的类是否包含虚函数,如果包含虚函数,那么可能有一个与我们的类正好同名的虚函数,这样我们的函数就被虚化了。
  40. 解决这个问题可以不直接从空类继承,而是创建一个中间类,并让这种类来继承空类,这样可以将影响限制在我们的中间类中。并将这个中间类的对象作为成员保存。
  41. [cpp] view plaincopyprint?
  42. class empty {};
  43. class foo : public empty {}; // not always correct
  44.  
  45. class foo
  46. {
  47. class bar : public empty {};
  48. // ok, the interface of foo is not affected by the inheritance from empty;
  49. };
  50. stl中,大量用到了函数对象,并且有许多函数对象是空的,如果大量存储这些函数对象也是会造成一定的浪费的(为什么要存储?假设一下,哈哈)。
  51. 在《C++ template metaprogramming》中有这样一个例子:有一个类,实现一个简单的复合函数f(g(x))
  52. [cpp] view plaincopyprint?
  53. template<typename R, typename F, typename G>
  54. class composed_fg
  55. {
  56. public:
  57. composed_fg(const F& f, const G& g)
  58. : f(f)
  59. , g(g)
  60. {}
  61.  
  62. template<typename T>
  63. R operator ()(const T& t) const
  64. {
  65. return f(g(t));
  66. }
  67. private:
  68. F f;
  69. G g;
  70. };
  71. 这里如果f或者g是空类,那么就会造成空间的浪费,视编译器而定,composed_fg最多可能会在32-bit平台上占用8字节。但是我们进行空基类优化,当f或者g中有空基类时,我们选择不同的实现。
  72. boost.compressed_pair就实现了一个优化过的std.pair,我们来分析一下boost.compressed_pair的实现。
  73. compressed_pair根据T1, T2的类型,来选择不同的实现,有6种情况
  74. T1 == T2 T1 empty T2 empty
  75. false false false
  76. false true false
  77. false false true
  78. false true true
  79. true false false
  80. true true true
  81. 其中区分T1==T2是因为,C++不允许有2个相同的直接基类。
  1. 这个是T1T2都不为空的情况,这里只是简单地在对象中保存了2个成员对象。再来看一下其中一个为空的情况。
  1. template <class T1, class T2>
  2. class compressed_pair_imp<T1, T2, >
  3. : protected ::boost::remove_cv<T1>::type
  4. {
  5. public:
  6. typedef T1 first_type;
  7. typedef T2 second_type;
  8. typedef typename call_traits<first_type>::param_type first_param_type;
  9. typedef typename call_traits<second_type>::param_type second_param_type;
  10. typedef typename call_traits<first_type>::reference first_reference;
  11. typedef typename call_traits<second_type>::reference second_reference;
  12. typedef typename call_traits<first_type>::const_reference first_const_reference;
  13. typedef typename call_traits<second_type>::const_reference second_const_reference;
  14.  
  15. compressed_pair_imp() {}
  16.  
  17. compressed_pair_imp(first_param_type x, second_param_type y)
  18. : first_type(x), second_(y) {}
  19.  
  20. compressed_pair_imp(first_param_type x)
  21. : first_type(x) {}
  22.  
  23. compressed_pair_imp(second_param_type y)
  24. : second_(y) {}
  25.  
  26. void swap(::boost::compressed_pair<T1,T2>& y)
  27. {
  28. // no need to swap empty base class:
  29. cp_swap(second_, y.second());
  30. }
  31. private:
  32. second_type second_;
  33. };

这里T1为空,compressed_pair从T1继承了,然而T2还是作为成员保存起来了。还有一点变化就是swap中,只对second进行了操作,很显然,因为T1子对象是空的,swap没有意义。 
其他的情况类似了,所以可以自己去看boost的源码。

Side Note

VC中存在对Empty base class过度优化的情况,对于2个相同类型基类子对象的情况,在g++中,会生成2个字节大小的对象,而VC中只是1个字节的大小。

References

[1] The "Empty Member" C++ Optimization 
[2] Empty Base Class or Structure Assignment Operator May Corrupt Data 
[3] Understanding the Empty Base Optimization 
[4] 《C++ template metaprogramming》 
[5] Why is the size of an empty class not zero?

参考:http://blog.csdn.net/seizef/article/details/6168721

http://www.bubuko.com/infodetail-479676.html

http://www.cppblog.com/qinqing1984/archive/2011/07/10/150584.aspx

空基类优化empty base class optimization的更多相关文章

  1. 空基类优化—— EBCO—— empty base class optimization

    完全参考自:<C++ Templates The Complete Guide>e_CN,p_281 16.2 空基类优化 最近周围有点吵,论文没看进去,随便翻了本书…… 下文没有多大意义 ...

  2. [百度空间] [原] Empty base class optimization

    最近遇到了一个诡异的问题, 数组的数据不对, 最后发现是两个类型的大小不一样导致的. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ...

  3. C#继承机制 访问与隐藏基类成员

    (1) 访问基类成员 通过base 关键字访问基类的成员:   调用基类上已被其他方法重写的方法.  指定创建派生类实例时应调用的基类构造函数.  基类访问只能在构造函数.实例方法或实例属性访问器中进 ...

  4. 不可或缺 Windows Native (22) - C++: 多重继承, 虚基类

    [源码下载] 不可或缺 Windows Native (22) - C++: 多重继承, 虚基类 作者:webabcd 介绍不可或缺 Windows Native 之 C++ 多重继承 虚基类 示例1 ...

  5. 4.6 C++抽象基类和纯虚成员函数

    参考:http://www.weixueyuan.net/view/6376.html 总结: 在C++中,可以通过抽象基类来实现公共接口 纯虚成员函数没有函数体,只有函数声明,在纯虚函数声明结尾加上 ...

  6. C++中的抽象基类示例

    抽象基类(abstract base class,ABC)例子:圆与椭圆.建立一个基类BaseEllipse,建立它的恋歌继承了Ellipse和Circle.ellipse.h #ifndef ELL ...

  7. C# 关于接口与基类的理解(二者的区别)

    接口(接口的名称一般用大写字母I开头的)是把公共实例(非静态)方法和属性组合起来,以封装特定功能的一个集合.(其实,接口简单理解就是一种约定,使得实现接口的类或结构在形式上保持一致) 注意:使用接口可 ...

  8. Python的程序结构[2] -> 类/Class[1] -> 基类与继承

    基类与继承 / Base Class and Inheritance Class 面向对象的特性使得 Python 中不可避免地需要使用到类和类的继承,类的继承可以使得代码很好的被重用.下面以一些代码 ...

  9. C++派生类中如何初始化基类对象(五段代码)

    今天收到盛大的面试,问我一个问题,关于派生类中如何初始化基类对象,我在想派生类对于构造函数不都是先构造基类对象,然后在构造子类对象,但是如果我们在成员初始化列表先初始化派生类的私有成员,在函数内去调用 ...

随机推荐

  1. Unix系统编程()open,read,write和lseek的综合练习

    需求:程序的第一个命令行参数为将要打开的文件名称,余下的参数则指定了文件上执行的输入输出操作.每个表示操作的参数都以一个字母开头,紧跟以相关值(中间无空格分隔). soffet:从文件开始检索到off ...

  2. Control.DataBinding数据绑定细解

    在C#操作数据库过程中,针对一般的文本控件,比如TextBox,Label等,我们赋值直接使用类似TextBox.Text=****的方式 来进行,这种方式从某种意义上来说的确是最简便的方式,但是对于 ...

  3. TDS协议解析

    文章来自:http://freetds.cvs.sourceforge.net/*checkout*/freetds/freetds/doc/tds.html 该网站是免费的专门介绍TDS协议的,网址 ...

  4. net mvc 小目标

    1.前台视图去找指定的控制器(非默认) 2.控制器去找指定的视图(非默认)

  5. 使用jmeter实现对jar包的调用

    一.前言 在我们测试接口的过程中,可能有时需要用到第三方jar包来生成一些测试数据(如有时需要对参数的输入值使用第三方jar包进行加密操作),涉及到这种的情况,普遍做法是:手动调用jar包获得需要的值 ...

  6. Img src用base64数据

    <img src='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgG ...

  7. HDU 5326(2015多校3)-Work(dfs)

    题目地址:pid=5326">HDU 5326 题意:给一张有向图n个点.n - 1(....输入n-1)条边. A指向B代表A管理B.然后能够间接管理,比方A管理B,B管理C.则A管 ...

  8. 【python】pyqt练习

    import sys from PyQt4.QtCore import * from PyQt4.QtGui import * import ui_price class PriceDlg(QDial ...

  9. .net泛型通用函数的特殊问题的解决方法

    自从2.0版本的net framework推出之后泛型(Generic)得到了广泛好评.它不必像object类型一样性能上因为“拆箱”或者“装箱”得到损失,同时在编译语法检测阶段就可以实时检测出传入或 ...

  10. K-Means算法Demo

    简介:本Demo是参照这个网站上的Demo自己用Java实现的.将Java打包为Jar,再将Jar转为exe,源代码及程序Demo下载请点我. K-Means算法简介 我尽量用通俗易懂但不规范的语言来 ...