关闭

More Effective C++议题【四】:避免无用的缺省构造函数

标签: 默认构造函数More Effective C++placement newdefault constructorconstructor
797人阅读 评论(0) 收藏 举报
分类:

<<More Effective C++>> 学习笔记【四】

这个议题讨论的是缺省构造函数,在分析议题之前,我们首先通论两件关于缺省(默认)构造的事情:

  1. 缺省构造函数做什么?
  2. 缺省构造函数什么时候被调用?
这些答案我们去语言的说明中寻找,《The.C++.Programming.Language.4th.Edition》一书中在17.3.3 Default Constructors小节详细讨论了缺省构造函数,部分摘录原文如下:
“A constructor that can be invoked without an argument is called a default constructor. Default constructors are very common.”
--- 缺省构造函数就是不接受任何参数的构造函数。
”A default constructor is used if no arguments are specified or if an empty initializer list is provided.“
--- 对未指明参数的构造和空列表的构造会调用缺省构造函数。
"A default argument (§12.2.5) can make a constructor that takes arguments into a default constructor."
--- 带有参数默认值的构造函数也可以作为缺省构造函数。
”The built-in types are considered to have default and copy constructors.However, for a built-in type the default constructor is not invoked for uninitialized non-static variables“
--- 内嵌类型都具备缺省构造函数和拷贝构造函数,但对于未初始化的非静态内嵌类型变量,则不会调用缺省构造函数。(内嵌类型的显示初始化很重要!)
”References and consts must be initialized. Therefore, a class containing such members cannot be default constructed unless the programmer supplies in-class member initializers or defines a default constructor that initializes them.“
--- 含有引用和常量的类必须对它们在缺省构造函数中进行处理,因为引用和常量必须初始化。
”An array, a standard-library vector, and similar containers can be declared to allocate a number of default-initialized elements. In such cases, a default constructor is obviously required for a class used as the element type of a vector or array.“
示例:
struct S1 { S1(); }; // has default constructor
struct S2 { S2(string); }; // no default constructor
S1 a1[10]; // OK: 10 default elements
S2 a2[10]; // error : cannot initialize elements
S2 a3[] { "alpha", "beta" }; // OK: two elements: S2{"alpha"}, S2{"beta"}
vector<S1> v1(10); // OK: 10 default elements
vector<S2> v2(10); // error : cannot initialize elements
vector<S2> v3 { "alpha", "beta" }; // OK: two elements: S2{"alpha"}, S2{"beta"}
vector<S2> v2(10,""); // OK: 10 elements each initialized to S2{""}
vector<S2> v4; // OK: no elements
” When should a class have a default constructor? A simple-minded technical answer is ‘‘when you use it as the element type for an array, etc.’’ ”
---- 什么时候一个类需要缺省构造函数?Bjarne给出的答案是:“当你需要把它作为数组元素的时候。”
“However, a better question is ‘‘For what types does it make sense to have a default value?’’ or even ‘‘Does this type have a ‘special’ value we can ‘naturally’ use as a default?’’ String has the empty string, "", containers have the empty set, {}, and numeric values have zero.” “
”The trouble with deciding on a default Date (§16.3) arose because there is no ‘‘natural’’ default date (the Big Bang is too far in the past and not precisely associated with our everyday dates).“”

“It is a good idea not to be too clever when inventing default values.”

不是所有的对象的缺省构造都有意义,对于很多对象来说,不利用外部数据进行完全的初始化是不合理的。

但是,如果一个类没有缺省构造函数,就会存在一些使用上的限制。考虑如下代码:
class EquipmentPiece {
public:
    EquipmentPiece(int IDNumber);
    //...
};

//建立数组时,没有一种办法能在建立对象数组时给构造函数传递参数。所以在通常情况下,无法正确调用EquipmentPiece 构造函数
EquipmentPiece bestPieces[10]; // 错误!
EquipmentPiece *bestPieces = new EquipmentPiece[10]; // 错误!

1. 关于数组

虽然,两本书中都提到了对于数组的情况必须有缺省构造函数,但是通过显示的对数组成员调用带参构造函数则可以绕开这个问题,但这样做的结果则是构成了非堆数组(Non-heap array)。
int ID1, ID2, ID3, ..., ID10; // 存储设备ID号的
// 变量
...
EquipmentPiece bestPieces[] = { // 正确, 提供了构造函数的参数
EquipmentPiece(ID1),  
EquipmentPiece(ID2),
EquipmentPiece(ID3),
...,
EquipmentPiece(ID10)
};
这样做的实际内存被分配到了堆上。
但是如果一定要使用堆,则可以通过声明指针的数组来绕开这个问题,如下代码示例很常见,也许我们并不曾深入理解
typedef EquipmentPiece* PEP; // PEP 指针指向一个EquipmentPiece对象
    PEP bestPieces[10]; // 正确, 没有调用构造函数
    PEP *bestPieces = new PEP[10]; // 也正确
    for (int i = 0; i < 10; ++i)
    {
        bestPieces[i] = new EquipmentPiece( ID Number );
    }
《More Effective C++》议题中对这个方法提出了两个比较直白的缺点,但我个人并不认为这有什么缺点。
此外,(可能是为了秀技术高深吧)这个议题中还给出了另一个非常不常用的使用placement new的方法,在个人看来这种方法更类似于直接Malloc的操作,只不过更可控一些。所以我看来如果你遇到以为程序员压根就不知道这个placement new/delete,不必觉得他不C++ style,他只是更加务实而已。代码如下:
// 为大小为10的数组 分配足够的内存EquipmentPiece 对象; 详细情况请参见条款M8 operator new[] 函数
void *rawMemory = operator new[](10*sizeof(EquipmentPiece));

// make bestPieces point to it so it can be treated as an EquipmentPiece array
EquipmentPiece *bestPieces = static_cast<EquipmentPiece*>(rawMemory);

// construct the EquipmentPiece objects in the memory 使用"placement new" (参见条款M8)
for (int i = 0; i < 10; ++i)
{
    new (&bestPieces[i]) EquipmentPiece( ID Number );
}
//博主:这里多数人的习惯写法其实会这样,当然,这其实从C++来讲毫无意义 
EquipmentPiece *bestPieces = (Equipment*) malloc(10* sizeof(EquipmentPiece));
本议题的学习中不想过多研究placement new/delete,虽然它们存在,但是恐怕用者寥寥。等到议题【八】再做详细学习。


2. 关于模板

实例化一个模板时,模板的类型参数应该提供一个缺省构造函数,这是一个常见的要求。这个要求本质上其实仍然是数组,--- 来自于模板内部,被建立的模板参数类型数组里。
template<class T>
class Array {
public:
    Array(int size);
    //...
    private:
    T *data;
};
//构造函数实现
template<class T>
Array<T>::Array(int size)
{
    data = new T[size]; // 为每个数组元素依次调用 T::T()
}

前面在《The.C++.Programming.Language.4th.Edition》的摘录中可以看到该书对VECTOR中可能出现的关于没有缺省构造函数的问题进行了确认,这是出于不同实现的考虑。在《More Effective C++》中对此如下说明:
在多数情况下,通过仔细设计模板可以杜绝对缺省构造函数的需求。例如标准的vector模板(生成一个类似于可扩展数组的类)对它的类型参数没有必须有缺省构造函数的要求。不幸的是,很多模板类没有以仔细的态度去设计。这样,没有缺省构造函数的类就不能与许多模板兼容。当C++程序员深入领会了模板设计以后,这样的问题应该不再那么突出了。这会花多长时间,完全在于个人的造化。
这种事情说白了见仁见智,无定论无结论了。

3. 虚基类

不提供缺省构造函数的虚基类,很难与其进行合作。因为几乎所有的派生类在实例化时都必须给虚基类构造函数提供参数。这就要求所有由没有缺省构造函数的虚基类继承下来的派生类(无论有多远)都必须知道并理解提供给虚基类构造函数的参数的含义。派生类的作者是不会企盼和喜欢这种规定的。

书中结论:

如果一个类的构造函数能够确保所有的部分被正确初始化,所有这些弊病都能够避免。缺省构造函数一般不会提供这种保证,所以在它们可能使类变得没有意义时,尽量去避免使用它们。使用这种(没有缺省构造函数的)类的确有一些限制,但是当你使用它时,它也给你提供了一种保证:你能相信这个类被正确地建立和高效地实现。

又及:
这个建议其实很勉强,而且很模棱两可。你无法从建议中找到具体该怎样做,相反,笔者个人更加支持
The.C++.Programming.Language.4th.Edition》中的更加明确的建议:
  • 为数组、模板提供默认构造函数。
  • 其他时候,在确保默认构造函数足够正确有效并保证成员初始化的完整性前提下根据现实世界的实际建模需求去设计它。

};
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场