重叠和委托构造函数
具有重叠功能的构造函数
实例化新对象时,C ++编译器会隐式调用该对象的构造函数。具有多个具有重叠功能的构造函数的类并不罕见。考虑以下类:
class Foo
{
public:
Foo()
{
// 代码A
}
Foo(int value)
{
// 代码 A
// 代码B
}
};
这个类有两个构造函数:一个默认构造函数和一个带整数的构造函数。因为构造函数的“代码执行A”部分是两个构造函数都需要的,所以代码在每个构造函数中都是重复的。
正如你(希望)现在所学到的那样,尽可能避免重复代码,所以让我们来看看解决这个问题的一些方法。
显而易见的解决方案在C ++ 11之前不起作用
显而易见的解决方案是让Foo(int)构造函数调用Foo()构造函数来执行A部分。
class Foo
{
public:
Foo()
{
// 代码 A
}
Foo(int value)
{
Foo(); // 使用上面的构造函数来做A部分代码(不能工作)
// 代码 B
}
};
``
要么
class Foo
{
public:
Foo()
{
// 代码 A
}
Foo(int value): Foo() // 使用上面的构造函数来做A代码( C++11之前的编译器不能工作)
{
// 代码 B
}
};
但是,使用pre-C ++ 11编译器,如果你试图让一个构造函数调用另一个构造函数,它通常会编译,但它不会像你期望的那样工作,你可能会花很长时间试图弄清楚为什么,即使使用调试器。
(说明:在C ++ 11之前,从另一个构造函数显式调用构造函数创建一个临时对象,使用构造函数初始化临时对象,然后丢弃它,保持原始对象不变)
使用单独的功能
构造函数都可以调用非构造函数的类。请注意,非构造函数使用的任何成员都已初始化。虽然您可能想要将代码从第一个构造函数复制到第二个构造函数中,但是使用重复的代码会使您的类更难理解并且维护起来更加繁重。这个问题的最佳解决方案是创建一个非构造函数,它执行常见的初始化,并让两个构造函数都调用该函数。
鉴于此,我们可以将上面的类更改为以下类:
class Foo
{
private:
void DoA()
{
// 代码 A
}
public:
Foo()
{
DoA();
}
Foo(int nValue)
{
DoA();
// 代码 B
}
};
通过这种方式,代码重复保持最小化。
相应地,您可能会发现自己处于要编写成员函数以将类重新初始化为默认值的情况。因为您可能已经有一个构造函数来执行此操作,您可能会尝试从您的成员函数中调用构造函数。但是,尝试直接调用构造函数通常会导致意外行为。许多开发人员只是在初始化函数中复制构造函数中的代码,这会起作用,但会导致代码重复。在这种情况下,最好的解决方案是将代码从构造函数移动到新函数,并让构造函数调用您的函数来执行初始化数据的工作:
class Foo
{
public:
Foo()
{
Init();
}
Foo(int value)
{
Init();
// 做有一些事情
}
void Init()
{
// 初始化代码 Foo
}
};
包含一个Init()函数是很常见的,它将成员变量初始化为默认值,然后让每个构造函数在执行特定于参数的任务之前调用Init()函数。这样可以最大限度地减少代码重复,并允许您从任何地方显式调用Init()。
小警告:使用Init()函数和动态分配内存时要小心。因为任何人都可以随时调用Init()函数,所以在调用Init()时可能已经或可能没有分配动态分配的内存。小心处理这种情况 - 它可能会有点混乱,因为非空指针可能是动态分配的内存或未初始化的指针!
在C ++ 11中委托构造函数
从C ++ 11开始,现在允许构造函数调用其他构造函数。此过程称为委托构造函数(或构造函数链接)。
要让一个构造函数调用另一个构造函数,只需在成员初始化列表中调用构造函数即可。这是直接调用另一个构造函数的一种情况。应用于上面的示例:
class Foo
{
private:
public:
Foo()
{
// 代码o A
}
Foo(int value): Foo() // 用Foo() 默认构造函数完成A
{
// 代码B
}
};
这完全符合您的预期。确保从成员初始值设定项列表中调用构造函数,而不是在构造函数的主体中。
这是使用委托构造函数来减少冗余代码的另一个示例:
#include <string>
#include <iostream>
using namespace std;
class Employee
{
private:
int m_id;
std::string m_name;
public:
Employee(int id = 0, const std::string &name = "") :
m_id(id), m_name(name)
{
std::cout << "Employee " << m_name << " created.\n";
}
// 使用委派构造函数来最小化冗余代码
Employee(const std::string &name) : Employee(0, name) //如果是委托构造函数,这里是不能在初始化其他的变量的
{ }
void print();
};
void Employee::print()
{
cout << "myID:" << m_id <<endl<< "myname:" << m_name << endl;
}
int main()
{
Employee Joe("mawei");
Joe.print();
return 0;
}
这个类有2个构造函数,其中一个委托给Employee(int,const std :: string&)。通过这种方式,冗余代码的数量被最小化(我们只需要编写一个构造函数体而不是两个)。
关于委托构造函数的一些附加说明。首先,不允许委托给另一个构造函数的构造函数进行任何成员初始化。所以你的构造函数可以委托或初始化,但不能同时委托或初始化。
其次,一个构造函数可以委托给另一个构造函数,该构造函数委托给第一个构造函数。这形成了一个无限循环,并将导致程序耗尽堆栈空间并崩溃。您可以通过确保所有构造函数都解析为非委托构造函数来避免这种情况。