Aggregate类型以及值初始化

引言

在C++中,POD是一个极其重要的概念。要理解POD类型,我们首先需要理解Aggregate类型。下文根据stackoverflow上的回答将对Aggregate类型做一个全面的解读。

对于Aggragates的定义

C++标准(C++ 03 8.5.1 §1)中的正式定义如下:

An aggregate is an array or a class (clause 9) with no user-declared constructors (12.1), no private or protected non-static data members (clause 11), no base classes (clause 10), and no virtual functions (10.3).

译文:一个Aggregate是一个数组或者一个没有用户声明构造函数,没有私有或保护类型的非静态数据成员,没有父类和虚函数的类型

从定义中我们首先可以解读出以下两点信息:

1. 数组一定是Aggregate。

2. 满足以下定义的class也可以是Aggregate(注意:class包括classes、structs和unions):

-> Aggregate不能拥有用户自定义的构造函数。事实上,它可以拥有一个默认构造函数或者一个默认复制构造函数,只要它们是被编译器声明,而非用户自定义的即可。

-> Aggregate不能拥有private或者protected的非静态数据成员。事实上,你可以定义其他private和protected成员方法(不包括构造函数,对于Aggregate,不能由用户自定义构造函数,参见上条),也可以定义private和protected静态类型的数据成员和方法,这都不会违背aggregate类型的规则。

-> Aggregate没有父类和虚函数。

-> Aggregate类型可以由用户自定义赋值操作符和析构函数。

注意:数组一定是Aggregate类型,即便数组中存放的是非Aggregate类型的元素。

例子

class NotAggregate1
{
  virtual void f(){} //remember? no virtual functions
};

class NotAggregate2
{
  int x; //x is private by default and non-static
};

class NotAggregate3
{
  public:
  NotAggregate3(int) {} //oops, user-defined constructor
};

class Aggregate1
{
  public:
  NotAggregate1 member1;   //ok, public member
  Aggregate1& operator = (Aggregate1 const & rhs) {/* */} //ok, copy-assignment
  private:
  void f() {} // ok, just a private function
};

Why Aggregates are special?

与非Aggregate类型不同的是,Aggregate类型可以使用“{}”来进行初始化。这种初始化语法,实际上在数组中是很常见的。别忘了我们的数组类型也属于Aggregate,来看以下例子:

Type array_name[n] = {a1, a2, ..., am};

1. 如果m与n相等:很自然,数组的第i个元素被初始化为ai。

2. 如果m小于n   :数组的前m个元素被依次初始化为a1,a2,…,am,剩余的n-m个元素将进行值初始化(前提是可以进行值初始化)。

3. 如果m大于n   :产生编译错误。

注意,类似于以下形式也是正确的:

int a[] = {1,2,3};) // 等价于 int a[3] = {1,2,3};

数组的长度将由编译器推测为3,因此以上两种形式是等价的。

值初始化(value-initialization

先来看以下解释,摘自stackoverflow:

When an object of scalar type (bool, int, char, double, pointers, etc.) is value-initialized it means it is initialized with 0 for that type (false for bool, 0.0 for double, etc.). When an object of class type with a user-declared default constructor is value-initialized its default constructor is called. If the default constructor is implicitly defined then all nonstatic members are recursively value-initialized. This definition is imprecise and a bit incorrect but it should give you the basic idea. A reference cannot be value-initialized. Value-initialization for a non-aggregate class can fail if, for example, the class has no appropriate default constructor.

译文如下:

对于标量类型(如:bool、int、char、double、指针)的对象如果按值初始化,是指其将被初始化为0(如:bool类型将被初始化为false,double类型将被初始化为0.0)。

默认构造函数由用户自定义的类类型对象如果按值初始化,那么其默认构造函数将会被调用;如果默认构造函数为隐式定义,那么所有的非静态数据成员将会递归地按值初始化。

虽然以上定义并不精确,也不完全,但是可以让我们有个基本的认识。注意,引用不能按值初始化。对于非Aggregate类型的class进行值初始化,可能会失败,例如没有合适的默认构函数。

1. 来看以下数组初始化的例子:

class A()
{
   A(int){} // 用户自定义了构造函数,因此是非Aggrerate类型。注意,类A没有默认构造函数。定义对象时,无法进行值初始化
};
class B()
{
   B() {}   // 用户自定义了构造函数,因此是非Aggrerate类型。注意,类B拥有可用的默认构造函数。定义对象时,可以进行值初始化。
};

int main()
{
  A a1[3] = {A(2), A(1), A(14)}; // OK n == m
  A a2[3] = {A(2)};              // ERROR A没有默认构造函数. 不能按值初始化a2[1] 和 a2[2]
  B b1[3] = {B()};               // OK b1[1]和b1[2]使用默认构造函数按值初始化

  int Array1[1000] = {0};        // 所有元素被初始化为0
  int Array2[1000] = {1};        // 注意: 只有第一个元素被初始化为1,其他为0;
  bool Array3[1000] = {};        // 大括号里可以为空,所有元素被初始化为false;

  int Array4[1000];              // 没有被初始化. 这和空{}初始化不同;这种情形下的元素没有按值初始化,他们的值是未知的,不确定的; (除非Array4是全局数据)

  int array[2] = {1,2,3,4};      // ERROR, 太多初始值,编译出错。
}

2. 现在我们来看Aggregates类类型是如何使用{ }进行初始化的。和对数组进行初始化非常类似,按照在类内部声明的顺序(按照定义都必须是public类型)初始化非静态类型的成员变量。如果初始值比成员少,那么其他的成员将按值初始化。如果有一个成员无法进行按值初始化,我们将会得到一个编译期错误。如果初始值比成员多,我们同样得到一个编译期错误。

struct X{
  int i1;
  int i2;
};
struct Y{
  char c;
  X x;
  int i[2];
  float f;
protected:
  static double d;
private:
  void g(){}
}; 

Y y = {‘a‘, {10,20}, {20,30}};

上面的例子中,y.c被初始化为’a’,y.x.i1被初始化为10,y.x.i2被初始化为20,y.i[0]为20,y.i[1]为30,y.f被按值初始化为0.0。protected类型的static数据成员d不会被初始化,因为它是静态类型的。

总结

我们知道了Aggregates的特别之处,现在让我们来尝试理解一下它对类型的限制,也就是说为什么会有这些限制。来看以下解释,同样摘自stackoverflow:

We should understand that memberwise initialization with braces implies that the class is nothing more than the sum of its members. If a user-defined constructor is present, it means that the user needs to do some extra work to initialize the members therefore brace initialization would be incorrect. If virtual functions are present, it means that the objects of this class have (on most implementations) a pointer to the so-called vtable of the class, which is set in the constructor, so brace-initialization would be insufficient. You could figure out the rest of the restrictions in a similar manner as an exercise :)

我们应当理解使用{ }对成员进行逐一初始化意味着这一类型仅仅是成员的集合。

如果有一个用户定义的构造函数,那意味着用户需要做一些额外的工作来初始化成员,因此使用{ }初始化是不正确的。如果出现了虚函数,那意味着这个类型(大多数实现)有一个指向vtable的指针,需要在构造函数内设置,所以使用{ }初始化是不够的。我们可以按照这种方式理解其他限制的含义。

下篇博文,我们会通过Aggregates类型来介绍POD(原生数据类型)。

参考文献

1. What are Aggregates and PODs and how/why are they special?

时间: 2024-10-26 13:20:04

Aggregate类型以及值初始化的相关文章

个人学习C++过程中对const的总结:初始化系列之用字面值常量与其他类型的值初始化的区别(一)

const这个系列博大精深,在学习过程一点一点积累记录.但是由于随笔在发布之后不能修改,有了新的想法之后不能再在原随笔上修改,只好用一个个系列来慢慢积累. 哈哈,在发布之后发现是可以继续编辑的,好吧,不管了,这种方式挺好. 正文: 昨晚接触到一个算法题目,回文字符串,在网上找了某段代码,但是发现有点问题,原形大概如下: .... const int len=mystr.size(); //mystr是string的一个实例,其实我挺奇怪这里为什么用int而不是string::size_type或

理解隐式类型、对象初始化程序和匿名类型

在C# 3.0中,几乎每个新特性都是为LINQ服务的.所以,本文将介绍下面几个在C# 3.0中引入的新特性: 自动实现的属性 隐式类型的局部变量 对象和集合初始化程序 隐式类型的数组 匿名类型 其实这几个特性都是比较容易理解的,对于这几个特性,编译器帮我们做了更多的事情(想想匿名方法和迭代器块),从而简化我们的代码. 自动实现的属性 在C# 3.0以前,当我们定义属性的时候,一般使用下面的代码 public class Book { private int _id; private string

你根本不会Javascript(1)——类型、值和变量

文原载于szhshp.org/tech/2017/02/18/JavaSprite.html 转载请注明 类型.值和变量 包装对象和原始值 ECMAScript 有 5 种原始类型(primitive type) Undefined Null Boolean Number String 基本类型(null, undefined, bool, number, string)应该是值类型,没有属性和方法. 内置对象 Javascript有一系列内置对象来创建语言的基本功能,具体有如下几种 Boole

C# 类型基础 值类型和引用类型

引言 本文之初的目的是讲述设计模式中的 Prototype(原型)模式,但是如果想较清楚地弄明白这个模式,需要了解对象克隆(Object Clone),Clone其实也就是对象复制.复制又分为了浅度复制(Shallow Copy)和深度复制(Deep Copy),浅度复制和深度复制又是以如何复制引用类型成员来划分的.由此又引出了引用类型和值类型,以及相关的对象判等.装箱.拆箱等基础知识.索性从最基础的类型开始自底向上写起. 值类型和引用类型 先简单回顾一下C#中的类型系统.C# 中的类型一共分为

值初始化和默认初始化的区别

直接初始化和拷贝初始化 如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象中去.与之相反,如果不使用等号,则执行的是直接初始化. 当初始值只有一个时,使用直接初始化或拷贝初始化都行.如果用多个值进行初始化的情况,非要用拷贝初始化的方式处理也不是不可以,不过需要显式地创建一个(临时)对象用于拷贝. string s8=string(10,'c'); //拷贝初始化,s8的内容是cccccccccc. C++支持两种初始化形式:直接初始化和复制初始

<转>类型初始值设定项引发异常

类型初始值设定项引发异常 连续两天都为这个运行时错误“类型初始值设定项引发异常”而烦恼,调试也不知道哪里出 了问题.上网Google一下,一大堆相同的问题,可是按照那些方法折腾来折腾去,问题还是一样.最后在CSDN上发帖子问了,果然“重赏之下必有勇 夫”,很快就有高手回复了,问题也随着解决了.哈哈.在此写个随笔,以后如果大家遇到类似问题,也可参考一下,自己也做个备忘,不然放在电脑上,又找不 到,我的电脑文件到处乱放,有时连我自己都找不到^_^. 问题是这样嘀: 项目采用了三层架构和工厂模式,并借

《JS权威指南学习总结--第三章类型、值和变量》

第三章 类型.值和变量 内容要点 一.数据类型 1.在编程语言中,能够表示并操作的值的类型称做数据类型 2.JS的数据类型分为两类: 原始类型:数字.字符串和布尔值 对象类型 3.JS中有两个特殊的原始值:null(空)和undefined(未定义) 4.对象(object):是属性(property)的集合,每个属性都由"名/值对"构成. 5.函数:JS中定义的另一种特殊对象.函数是具有与它相关联的可执行代码的对象,通过调用函数来运行可执行的代码,并返回运算结果. 6.构造函数: 如

Lua语言基础(1) -- 类型与值

基础介绍 Lua是一种动态类型的语言.在语言中没有类型定义的语法,每个值都带有其自身的类型信息.在Lua中有8种基本类型,分别是: nil(空)类型 boolean(布尔)类型 number(数字)类型 string(字符串)类型 userdata(自定义类型) function(函数)类型 thread(线程)类型 table(表)类型 以上是Lua中的8中基本类型,我们可以使用type函数,判断一个值得类型,type函数返回一个对应类型的字符串描述.例如: 1 2 3 4 5 6 7 8 9

Lua(一)——类型与值

lua是一款小巧的脚本语言,是巴西里约热内卢天主教大学里的一个研究小组于1993年开发其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能.lua由标准C编写而成,几乎在所有操作系统和平台上都可以编译.运行.lua并没有提供强大的库,这是由它的定位决定的,所以lua不适合作为开发独立应用程序的语言,在游戏开发等领域使用较多. 1. 引子 1.1 保留字 保留字的概念不用多说,lua中的保留字如下21个: and break do else elseif end false fo