C++ 构造器比 C# 构造器更复杂。首先,有五种类型的构造器。虽然您很少会为任何特定的类或结构编写所有五种类型,但您确实需要知道它们是什么、它们做什么以及它们看起来像什么。如果没有,您可能会发现自己面临一些非常混乱的错误或编译器错误。
C++ 构造器比 C# 构造器更复杂的原因是 C++ 类可以有多种存储持续时间。默认情况下,C++ 对象具有值语义,而 C# 类具有引用语义。这里有一个例子:
Vehicle someVehicle = vehicle;
让我们假设vehicle
不是空的,而是一个类型为Vehicle,
的有效对象,并且Vehicle
类型是一个类类型,而不是一个结构或其他东西。
让我们将前面的代码语句视为 C# 代码。在 C# 中,vehicle
引用的对象位于垃圾收集器管理的堆中的某个地方。前面的代码语句将对该对象的引用存储在someVehicle
变量中,它是从对该对象的现有vehicle
引用中获取的。仍然只有该对象的一个实例有两个引用。
现在让我们将前面的代码语句视为 C++ 代码。在 C++ 中,vehicle
所指的对象很可能是一个自动持续时间对象,但也可能是一个静态持续时间对象,甚至是一个线程持续时间对象。默认情况下,前面的代码语句会创建一个vehicle
对象的副本,并将其存储在自动持续时间someVehicle
变量的地址中。它使用一种叫做复制赋值操作符的东西来实现这一点,它是复制构造器的近亲。
现在有两份,而以前只有一份。除非 Vehicle 类有一个指针、std::shared_ptr
、一个引用或类似的东西作为成员变量,否则这两个副本是完全分开的。改变甚至摧毁一个对另一个没有任何影响。
当然,如果你想让someVehicle
成为对vehicle
的对象的引用,你可以稍微修改一下代码来完成。但是,如果你想让someVehicle
成为vehicle
,让vehicle
停止存在——被摧毁而不拿走它以前的资源,那该怎么办?C++ 通过一种叫做移动构造器和移动赋值操作符的东西使这成为可能。
如果您想有意禁用复制语义(统称为赋值和构造)或移动语义,这也是可能的。
我们将从默认构造器开始。可以在没有参数的情况下调用默认构造器。一个类只能有一个默认构造器。如果您定义了一个类,但不包含任何构造器,那么您将生成一个隐式默认构造器,编译器将为您创建该构造器。但是,如果类成员变量是没有默认构造器的类、结构或联合,则必须至少定义一个构造器,因为编译器无法创建隐式默认构造器。
如果定义了不带参数的构造器,则它是显式默认构造器。还可以定义一个接受参数的构造器,并使用默认参数使其成为默认构造器,我们稍后将讨论这一点。
如果您定义的构造器至少有一个必需的参数,那么编译器将不会生成默认的构造器。如果需要,必须定义显式默认构造器。
C++ 中的构造器,以及一般的函数,可以为其部分或全部参数指定默认参数作为声明的一部分(类似于 C# 的可选参数)。在 C++ 函数中,所有带有默认参数的参数必须出现在函数声明中没有默认参数的任何参数的右侧。C++ 不支持 C# 风格的命名参数,但是可以使用命名参数习惯用法完成类似的事情。
为什么在这里提到默认参数?事实证明,如果你有一个所有参数都有默认参数的构造器,那么这个构造器就是一个默认构造器。这是有意义的,因为如果您有一个带有签名Vehicle(VehicleType type = VehicleType::Car, double odometerReading = 0.0);
的构造器,那么您可以用空括号调用该构造器,并且将应用这些默认参数。如果定义默认构造器,则只能有一个,而不管它是否没有参数,或者它的参数是否都有默认参数。
这个原则更进一步。在同一个作用域中声明的两个同名函数不能在完全相同的位置具有相同的参数类型。您猜对了:出于不同函数签名的目的,带有默认参数的参数被忽略。
这一切都是有意义的,因为默认的论点使得无法区分double Add(double a, double b = 0.0);
和double Add(double a, int a = 0);
,因为两者都可以被称为double dbl = Add(5.0);
。在这种情况下,编译器无法知道您想要的是什么,因此它无法编译并显示错误消息。
参数化构造器有一个或多个参数。所有参数都有默认参数的参数化构造器也是类的默认构造器。C++ 中的参数化构造器没有什么特别之处。
转换构造器至少有一个参数。如果有多个,那么这些附加参数必须有默认参数。
如果不想让构造器成为转换构造器,可以用函数说明符explicit
来标记。让我们看一个例子:
#include <string>
class SomeClass
{
public:
SomeClass(const wchar_t* value) :
m_strValue(value),
m_intValue()
{
}
explicit SomeClass(int value) :
m_strValue(),
m_intValue(value)
{
}
~SomeClass(void) { }
const wchar_t* GetStringValue(void)
{
return m_strValue.c_str();
}
int GetIntValue(void) { return m_intValue; }
private:
std::wstring m_strValue;
int m_intValue;
};
void DoSomething(void)
{
// Normal constructor use.
SomeClass sc1 = SomeClass(L"Hello World");
// Fine because the const wchar_t* constructor
// is a conversion constructor.
SomeClass sc2 = L"Hello World";
// Normal constructor use.
SomeClass sc3 = SomeClass(1);
//// Illegal since the int constructor is not a
//// conversion constructor.
//SomeClass sc4 = 1;
// ...
}
如您所见,转换构造器允许我们通过直接将其设置为字符串值来构造s2
。编译器会看到该语句,检查SomeClass
是否有一个转换构造器来接收该类值,并继续调用适当的SomeClass
构造器。如果我们用注释掉的sc4
行来尝试,编译器会失败,因为我们用explicit
告诉编译器,只取int
的构造器不应该被视为转换构造器,而应该像任何其他参数化构造器一样。
转换构造器可能很有用,但也会导致错误。例如,当您只是输入了错误的变量名,而实际上是指赋值时,您可能会意外地创建一个新对象并将其赋值给现有变量。如果有有效的转换构造器,编译器不会抱怨,但是如果没有,编译器会抱怨。因此请记住这一点,并记住将单参数构造器标记为explicit,
,除非您有充分的理由提供转换构造器。
至此,我们已经看到了相当多的构造器,因此讨论您遇到的奇怪语法非常重要。让我们检查一个示例:
| | 注意:这个示例使用了一些非常糟糕的代码实践来说明 C++ 如何执行初始化。具体来说,构造器定义中初始值设定项的顺序在某些地方会产生误导。始终确保您的构造器按照程序执行期间实际发生初始化的顺序来安排基类和参数的初始化。这将帮助您避免错误,并使您的代码更容易调试和遵循。 |
示例:初始化示例\初始化示例. cpp
#include <iostream>
#include <ostream>
#include "../pchar.h"
using namespace std;
int CallingMsg(const wchar_t* cls)
{
wcout << L"Calling " << cls << L" constructor." << endl;
return 0;
}
int InitializingIntMsg(int value, const wchar_t* mvarName)
{
wcout << L"Initializing " << mvarName << L"." << endl;
return value;
}
class A
{
public:
A(void) :
m_value(InitializingIntMsg(5, L"DEFAULT m_value"))
{
wcout << L"DEFAULT Constructing A. m_value is '" <<
m_value << L"'." << endl;
}
explicit A(int) :
m_value(InitializingIntMsg(20, L"m_value"))
{
wcout << L"Constructing A. m_value is '" <<
m_value << L"'." << endl;
}
virtual ~A(void)
{
wcout << L"Destroying A." << endl;
}
private:
int m_value;
};
class B : virtual public A
{
public:
explicit B(int) :
A(CallingMsg(L"A")),
m_b(InitializingIntMsg(2, L"m_b")),
m_a(InitializingIntMsg(5, L"m_a"))
{
wcout << L"Constructing B. m_a is '" <<
m_a << L"' and m_b is '" << m_b << L"'." << endl;
}
virtual ~B(void)
{
wcout << L"Destroying B." << endl;
}
private:
int m_a;
int m_b;
};
class C
{
public:
explicit C(int) :
m_c(InitializingIntMsg(0, L"m_c"))
{
wcout << L"Constructing C. m_c is '" <<
m_c << L"'." << endl;
}
virtual ~C(void)
{
wcout << L"Destroying C." << endl;
}
private:
int m_c;
};
class D
{
public:
explicit D(int) :
m_d(InitializingIntMsg(3, L"m_d"))
{
wcout << L"Constructing D. m_d is '" <<
m_d << L"'." << endl;
}
virtual ~D(void)
{
wcout << L"Destroying D." << endl;
}
private:
int m_d;
};
class Y : virtual public B, public D, virtual public C
{
public:
explicit Y(int value) :
C(CallingMsg(L"C")),
m_someInt(InitializingIntMsg(value, L"m_someInt")),
D(CallingMsg(L"D")),
B(CallingMsg(L"B"))
{
wcout << L"Constructing Y. m_someInt is '" <<
m_someInt << L"'." << endl;
}
virtual ~Y(void)
{
wcout << L"Destroying Y." << endl;
}
int GetSomeInt(void) { return m_someInt; }
private:
int m_someInt;
};
class Z : public D, virtual public B, public C
{
public:
explicit Z(int value) :
D(CallingMsg(L"D")),
A(CallingMsg(L"A")),
C(CallingMsg(L"C")),
m_someInt(InitializingIntMsg(value, L"m_someInt")),
B(CallingMsg(L"B"))
{
wcout << L"Constructing Z. m_someInt is '" <<
m_someInt << L"'." << endl;
}
virtual ~Z(void)
{
wcout << L"Destroying Z." << endl;
}
int GetSomeInt(void) { return m_someInt; }
private:
int m_someInt;
};
int _pmain(int /*argc*/, _pchar* /*argv*/[])
{
{
Y someY(CallingMsg(L"Y"));
wcout << L"Y::GetSomeInt returns '" <<
someY.GetSomeInt() << L"'." << endl;
}
wcout << endl << "Break between Y and Z." << endl
<< endl;
{
Z someZ(CallingMsg(L"Z"));
wcout << L"Z::GetSomeInt returns '" <<
someZ.GetSomeInt() << L"'." << endl;
}
return 0;
}
我们所做的第一件事是定义两个编写消息的助手函数,这样我们就可以很容易地遵循事情发生的顺序。你会注意到每个类都有一个接受int
的构造器,尽管只有Y
和Z
使用它。A
、B
、C
和D
甚至没有在其int
参数构造器中为int
指定名称;他们只是指定有一个int
参数。这是完全合法的 C++ 代码,我们一直在_pmain
中使用它和注释掉的参数名。
类A
有两个构造器:一个默认构造器和一个采用int
的参数化构造器。剩下的类只有参数化的构造器,每个构造器都取一个int
。
- 类
B
无形中继承了A
。 - 类
C
从无中继承。 - 类
D
从无中继承。 - 类
Y
从B
虚拟继承,直接从D
继承,从C
虚拟继承,顺序如此。 - 类
Z
也是直接从D
继承,无形中从B
继承,直接从C
继承,顺序如此。
让我们看看这个程序给我们的输出,然后讨论发生了什么以及为什么。
Calling Y constructor.
Initializing DEFAULT m_value.
DEFAULT Constructing A. m_value is '5'.
Calling B constructor.
Initializing m_a.
Initializing m_b.
Constructing B. m_a is '5' and m_b is '2'.
Calling C constructor.
Initializing m_c.
Constructing C. m_c is '0'.
Calling D constructor.
Initializing m_d.
Constructing D. m_d is '3'.
Initializing m_someInt.
Constructing Y. m_someInt is '0'.
Y::GetSomeInt returns '0'.
Destroying Y.
Destroying D.
Destroying C.
Destroying B.
Destroying A.
Break between Y and Z.
Calling Z constructor.
Calling A constructor.
Initializing m_value.
Constructing A. m_value is '20'.
Calling B constructor.
Initializing m_a.
Initializing m_b.
Constructing B. m_a is '5' and m_b is '2'.
Calling D constructor.
Initializing m_d.
Constructing D. m_d is '3'.
Calling C constructor.
Initializing m_c.
Constructing C. m_c is '0'.
Initializing m_someInt.
Constructing Z. m_someInt is '0'.
Z::GetSomeInt returns '0'.
Destroying Z.
Destroying C.
Destroying D.
Destroying B.
Destroying A.
在我们的_pmain
函数中,首先我们在自己的范围内创建一个 Y 类型的对象。然后我们调用它的GetSomeInt
成员函数。这有助于确保编译器不会在发布版本中优化 Y 的创建,以防您乱动代码。它也是建设和破坏之间的标志。
然后我们退出 Y 的范围,触发它的毁灭。之后,我们编写另一个标记字符串,将 Y 实例的实例化与后面的 Z 实例的实例化分开。我们在它自己的范围内创建 Z 实例,这样我们就可以像使用 y 一样遵循它的构造和破坏。
我们看到了什么?相当多。先关注 Y 吧。
当我们调用 Y 构造器时,它做的第一件事是调用 a 的默认构造器。这可能看起来非常错误,原因有几个,但事实上,它是对的。Y 构造器要求遵循以下顺序:
- 初始化基类 c。
- 初始化 Y 的成员变量
m_someInt
。 - 初始化基类 d。
- 初始化基类 b。
相反,我们以这个顺序结束:
- 通过基类的默认构造器初始化基类。
- 初始化基类 b。
- 初始化基类 c。
- 初始化基类 d。
- 初始化 Y 的成员变量
m_someInt
。
既然我们知道 B 实际上是从 A 继承的,并且 B 是从 A 继承的唯一来源,我们就可以得出结论,B 优先于其他人,A 优先于 B。
嗯,我们先从 B 继承,然后再从其他类继承。可能是这样,但是为什么 D 不在 B 之后初始化呢?因为 D 是直接继承的,C 是虚拟继承的。美德第一。
以下是规则:
- 虚拟基类是按照从左到右的顺序构造的,因为它们被写入基类列表中。
- 如果没有为实际继承的基类调用特定的构造器,编译器将在适当的时候自动调用其默认构造器。
- 当确定基类的构造顺序时,基类在其派生类之前被初始化。
- 当虚拟基类全部构造完成后,直接基类将按照从左到右的声明顺序构造——与虚拟基类相同。
- 当它的所有基类都已构造完成时,类的成员变量为:
- 如果没有初始值设定项,则默认初始化。
- 如果初始值设定项是一组空的括号,则值被初始化。
- 初始化为初始值设定项括号内的表达式的结果。
- 成员变量按照它们在类定义中声明的顺序初始化。
- 当构造器中的所有初始值设定项都已运行时,构造器体中的任何代码都将被执行。
默认初始化不会对基本类型和指针进行初始化。它调用类类型的默认构造器。整数、指针等将只具有任意值。
值初始化将基本类型初始化为零或等效类型(例如,bool
为false
,指针为空)。它调用类类型的默认构造器。
当你把所有这些规则放在一起时,你最初会发现顺序是 B,C,D,因为 B 和 C 有虚拟继承,所以在 D 之前。然后我们在 B 之前加上 A,因为 B 源自 A。所以我们以 A,B,C,D 结束
因为基类是在派生类之前初始化的规则,并且因为 A 是通过虚拟继承进入的,所以在我们到达我们调用的 B 构造器中的初始值设定项之前,A 就已经用它的默认构造器初始化了。一旦我们到达了 B 构造器,因为 A 已经被初始化了,它在 B 构造器中的初始化器就被忽略了。
类 B 的成员变量按照 m_a,m_b 的顺序初始化,因为这是它们在类中声明的顺序,尽管在构造器中我们以相反的顺序列出了它们的初始化。
| | 注意:Visual C++ 不支持在 Visual Studio 2012 RC 中委托构造器。 |
委托构造器调用同一类的另一个构造器(目标构造器)。委托构造器只能有一个初始值设定项语句,即对目标构造器的调用。它的身体可以有语句;这些将在目标构造器完全完成后运行。这里有一个例子:
#include <iostream>
#include <ostream>
using namespace std;
class SomeClass
{
public:
SomeClass(void) : SomeClass(10)
{
// Statements here will execute after the
// statements in the SomeClass(int) constructor
// body have finished executing.
wcout << "Running SomeClass::SomeClass(void)." << endl;
}
SomeClass(int value)
: m_value(value)
{
// Statements here will execute after the m_value
// initializer above.
wcout << "Running SomeClass::SomeClass(int)." << endl;
}
int GetValue(void) { return m_value; }
private:
int m_value;
};
int main(int argc, char* argv[])
{
SomeClass someC;
wcout << L"SomeClass::GetValue() = " << someC.GetValue() << endl;
return 0;
}
如果您使用编译器(如 GCC)编译并运行该代码,您将看到以下输出:
Running SomeClass::SomeClass(int).
Running SomeClass::SomeClass(void).
SomeClass::GetValue() = 10
复制构造器只有一个强制参数:对与构造器的类具有相同类型的变量的引用。复制构造器可以有其他参数,只要它们都带有默认参数。它的目的是允许您构建一个对象的副本。
如果可以的话,编译器会为您提供一个默认的复制构造器,只要作为类、结构或联合的所有成员变量都有一个复制构造器,它就可以。它提供的复制构造器是一个浅复制构造器。例如,如果您有一个指向数据数组的指针,副本将获得指针的副本,而不是包含相同数据副本的新数组。
如果在该类的析构器中有一个 delete 语句,那么当另一个副本被销毁时,您将有一个副本处于无效状态,当您第二次尝试删除内存时,当剩余的副本被销毁时,您将有一个运行时错误,假设您的程序还没有崩溃。这是您应该始终使用智能指针的众多原因之一。我们将在 RAII 的章节中介绍它们。
如果您不希望使用编译器提供的复制构造器,或者如果编译器不能提供,但您仍然想要一个,您可以编写一个复制构造器。例如,也许你想要一个更深层次的数据副本,或者也许你的类有一个std::unique_ptr
,你决定一个什么样的可接受的“副本”对你的类是有用的。我们将在建筑样本中看到一个这样的例子。
复制构造器通常应该有以下声明:SomeClass(const SomeClass&);
。为了避免奇怪的错误,构造器应该总是使用*const*
左值引用您从中复制的类。没有理由在复制构造器中更改要复制的类。制作它const
没有害处,并且为你的操作提供了一些保证。复制构造器不应定义为explicit
。
如果定义了自定义复制构造器,还应该定义自定义复制赋值运算符。该运算符的结果应该是返回值是它正在复制的类的副本。这是当您有一个语句时调用的,如a = b;``a
和b
都是同一类型(如SomeClass)
)。
此运算符是其类的非静态成员函数,因此仅当您向类的现有实例进行复制分配时才会调用它。如果你有类似SomeClass a = b;
的东西,那么它将是一个复制构造,而不是一个复制任务。
副本分配运算符应具有以下声明:SomeClass& operator=(const SomeClass&);
。
在 C++ 中,如果你只有一个复制构造器,并且你想把一个类实例传递到一个std::vector
(类似于. NET List<T>
)或者从一个函数返回它,你需要复制它。即使你没有打算再次使用它,你仍然会招致制作副本所花费的时间。如果你给一个std::vector
添加了很多元素,或者你写了一个你经常使用的工厂函数,这将是一个巨大的性能提升。
这就是为什么 C++ 有一个移动构造器。移动构造器在 C++ 11 中是新的。在某些情况下,编译器会为您提供一个,但是通常您应该为您需要的类编写自己的。
这很容易做到。请注意,如果您编写了一个移动构造器,那么您还需要编写一个复制构造器。从 Visual Studio 2012 RC 开始,Visual C++ 不强制执行此规则,但它是 C++ 语言标准的一部分。如果您需要用另一个编译器编译您的程序,您应该确保在编写移动构造器时编写复制构造器和复制赋值运算符。
移动构造器通常应该有以下声明:SomeClass(SomeClass&&);
。不可能是const
(因为我们会修改)或者explicit
。
std::move
函数帮助您编写移动构造器和移动赋值运算符。在<utility>
头文件里。它接受一个参数,并在适合移动的条件下返回。传入的对象将作为rvalue
引用返回,除非对其禁用了移动语义,在这种情况下,您将得到一个编译器错误。
无论何时为类编写移动构造器,都应该编写移动赋值运算符。该运算符的结果应该是返回值包含旧类的所有数据。为了避免代码重复,可以从移动构造器中调用适当的移动赋值运算符。
移动赋值运算符应该有以下声明:SomeClass& operator=(SomeClass&&);
。
如果需要删除复制或移动语义,有两种方法可以做到这一点。首先,要删除复制语义,您可以将复制构造器和复制赋值运算符声明为私有的,并且不实现它们。C++ 只关心一个函数的实现,如果你试图使用它。通过这样做,任何试图编译试图使用复制语义的程序的尝试都将失败,产生错误消息,说您试图使用类的私有成员,并且没有它的实现(如果您不小心在类本身中使用它)。使构造器和赋值操作符私有的相同模式同样适用于移动语义。
第二种方法是 C++ 11 的新方法,目前 Visual C++ 不支持这种方法。通过这种方式,您可以将功能声明为显式删除。例如,要显式删除复制构造器,您可以编写SomeClass(const SomeClass&) = delete;
。相同的= delete
语法适用于赋值运算符、移动构造器和任何其他功能。但是,在 Visual C++ 支持之前,您需要坚持第一种方法。
任何作为另一个类的基类的类都应该有一个虚拟析构器。您可以使用virtual
关键字声明一个虚拟析构器(例如:virtual ~SomeClass(void);)
)。这样,如果您将一个对象向下转换为它的一个子类,然后销毁该对象,将调用适当的构造器,确保该类捕获的所有资源都被释放。
合适的析构器对于 RAII 这个习惯用法至关重要,我们将很快探索它。除非在析构器中捕获并处理异常,否则绝不允许在析构器中引发异常。如果出现您无法处理的异常,您应该执行安全错误日志记录,然后退出程序。您可以使用<exception>
头文件中的std::terminate
函数来调用当前的终止处理程序。默认情况下,终止处理程序从<cstdlib>
头调用abort
函数。我们将在探索 C++ 标准库异常时进一步讨论这个功能。
运算符重载是 C++ 的一个强大的高级特性。您可以在每个类的基础上重载运算符,也可以使用独立函数全局重载运算符。C++ 中几乎每个运算符都可以重载。我们将很快看到重载复制赋值、移动赋值、&、|、和|=运算符的例子。关于您可以重载的其他操作符的列表,以及这种重载是如何工作的,我建议访问关于主题的 MSDN 文档。这是一个有用的功能,但你可能不常用。当你需要的时候查找它通常比试图马上记住它要快。
| | 提示:不要重载一个操作符,给它一个可能会混淆和违背某人对重载作用的预期的含义。例如,
+
运算符通常应该执行加法或串联运算。让它做减法、除法、除法、乘法或其他看起来奇怪的事情会让其他人感到困惑,并带来很大的潜在 bug。这并不意味着您不应该在特定情况下使用没有明确语义的运算符。C++ 标准库中的std::wcout
和std::wcin
输入/输出功能将>>
和<<
操作符用于写入和读取数据。由于位移位在应用于流时没有特定的意义,以这种方式重新调整这些操作符的用途看起来很奇怪和不同,但不会导致任何关于它们的用途的明显错误的结论。一旦理解了操作符在应用于流时的作用,重新调整它们的用途就为语言增加了功能,否则语言将需要更多的代码。 |
示例:构造器示例\风味
#pragma once
namespace Desserts
{
enum class Flavor
{
None,
Vanilla,
Chocolate,
Strawberry
};
inline Flavor operator|=(Flavor a, Flavor b)
{
return static_cast<Flavor>(static_cast<int>(a) | static_cast<int>(b));
}
inline const wchar_t* GetFlavorString(Flavor flavor)
{
switch (flavor)
{
case Desserts::Flavor::None:
return L"No Flavor Selected";
break;
case Desserts::Flavor::Vanilla:
return L"Vanilla";
break;
case Desserts::Flavor::Chocolate:
return L"Chocolate";
break;
case Desserts::Flavor::Strawberry:
return L"Strawberry";
break;
default:
return L"Unknown";
break;
}
}
}
示例:构造示例\浇头. h
#pragma once
#include <string>
#include <sstream>
namespace Desserts
{
class Toppings
{
public:
enum ToppingsList : unsigned int
{
None = 0x00,
HotFudge = 0x01,
RaspberrySyrup = 0x02,
CrushedWalnuts = 0x04,
WhippedCream = 0x08,
Cherry = 0x10
} m_toppings;
Toppings(void) :
m_toppings(None),
m_toppingsString()
{
}
Toppings(ToppingsList toppings) :
m_toppings(toppings),
m_toppingsString()
{
}
~Toppings(void)
{
}
const wchar_t* GetString(void)
{
if (m_toppings == None)
{
m_toppingsString = L"None";
return m_toppingsString.c_str();
}
bool addSpace = false;
std::wstringstream wstrstream;
if (m_toppings & HotFudge)
{
if (addSpace)
{
wstrstream << L" ";
}
wstrstream << L"Hot Fudge";
addSpace = true;
}
if (m_toppings & RaspberrySyrup)
{
if (addSpace)
{
wstrstream << L" ";
}
wstrstream << L"Raspberry Syrup";
addSpace = true;
}
if (m_toppings & CrushedWalnuts)
{
if (addSpace)
{
wstrstream << L" ";
}
wstrstream << L"Crushed Walnuts";
addSpace = true;
}
if (m_toppings & WhippedCream)
{
if (addSpace)
{
wstrstream << L" ";
}
wstrstream << L"Whipped Cream";
addSpace = true;
}
if (m_toppings & Cherry)
{
if (addSpace)
{
wstrstream << L" ";
}
wstrstream << L"Cherry";
addSpace = true;
}
m_toppingsString = std::wstring(wstrstream.str());
return m_toppingsString.c_str();
}
private:
std::wstring m_toppingsString;
};
inline Toppings operator&(Toppings a, unsigned int b)
{
a.m_toppings = static_cast<Toppings::ToppingsList>(static_cast<int>(a.m_toppings) & b);
return a;
}
inline Toppings::ToppingsList operator&(Toppings::ToppingsList a, unsigned int b)
{
auto val = static_cast<Toppings::ToppingsList>(static_cast<unsigned int>(a) & b);
return val;
}
inline Toppings::ToppingsList operator|(Toppings::ToppingsList a, Toppings::ToppingsList b)
{
return static_cast<Toppings::ToppingsList>(static_cast<int>(a) | static_cast<int>(b));
}
inline Toppings operator|(Toppings a, Toppings::ToppingsList b)
{
a.m_toppings = static_cast<Toppings::ToppingsList>(static_cast<int>(a.m_toppings) | static_cast<int>(b));
return a;
}
}
示例:构造器示例\IceCreamSundae.h
#pragma once
#include "Flavor.h"
#include "Toppings.h"
#include <string>
namespace Desserts
{
class IceCreamSundae
{
public:
IceCreamSundae(void);
IceCreamSundae(Flavor flavor);
explicit IceCreamSundae(Toppings::ToppingsList toppings);
IceCreamSundae(const IceCreamSundae& other);
IceCreamSundae& operator=(const IceCreamSundae& other);
IceCreamSundae(IceCreamSundae&& other);
IceCreamSundae& operator=(IceCreamSundae&& other);
~IceCreamSundae(void);
void AddTopping(Toppings::ToppingsList topping);
void RemoveTopping(Toppings::ToppingsList topping);
void ChangeFlavor(Flavor flavor);
const wchar_t* GetSundaeDescription(void);
private:
Flavor m_flavor;
Toppings m_toppings;
std::wstring m_description;
};
}
示例:constructors sample \ icecreamsundae . CPP
#include "IceCreamSundae.h"
#include <string>
#include <sstream>
#include <iostream>
#include <ostream>
#include <memory>
using namespace Desserts;
using namespace std;
IceCreamSundae::IceCreamSundae(void) :
m_flavor(Flavor::None),
m_toppings(Toppings::None),
m_description()
{
wcout << L"Default constructing IceCreamSundae(void)." <<
endl;
}
IceCreamSundae::IceCreamSundae(Flavor flavor) :
m_flavor(flavor),
m_toppings(Toppings::None),
m_description()
{
wcout << L"Conversion constructing IceCreamSundae(Flavor)." <<
endl;
}
IceCreamSundae::IceCreamSundae(Toppings::ToppingsList toppings) :
m_flavor(Flavor::None),
m_toppings(toppings),
m_description()
{
wcout << L"Parameter constructing IceCreamSundae(\
Toppings::ToppingsList)." << endl;
}
IceCreamSundae::IceCreamSundae(const IceCreamSundae& other) :
m_flavor(other.m_flavor),
m_toppings(other.m_toppings),
m_description()
{
wcout << L"Copy constructing IceCreamSundae." << endl;
}
IceCreamSundae& IceCreamSundae::operator=(const IceCreamSundae& other)
{
wcout << L"Copy assigning IceCreamSundae." << endl;
m_flavor = other.m_flavor;
m_toppings = other.m_toppings;
return *this;
}
IceCreamSundae::IceCreamSundae(IceCreamSundae&& other) :
m_flavor(),
m_toppings(),
m_description()
{
wcout << L"Move constructing IceCreamSundae." << endl;
*this = std::move(other);
}
IceCreamSundae& IceCreamSundae::operator=(IceCreamSundae&& other)
{
wcout << L"Move assigning IceCreamSundae." << endl;
if (this != &other)
{
m_flavor = std::move(other.m_flavor);
m_toppings = std::move(other.m_toppings);
m_description = std::move(other.m_description);
other.m_flavor = Flavor::None;
other.m_toppings = Toppings::None;
other.m_description = std::wstring();
}
return *this;
}
IceCreamSundae::~IceCreamSundae(void)
{
wcout << L"Destroying IceCreamSundae." << endl;
}
void IceCreamSundae::AddTopping(Toppings::ToppingsList topping)
{
m_toppings = m_toppings | topping;
}
void IceCreamSundae::RemoveTopping(Toppings::ToppingsList topping)
{
m_toppings = m_toppings & ~topping;
}
void IceCreamSundae::ChangeFlavor(Flavor flavor)
{
m_flavor = flavor;
}
const wchar_t* IceCreamSundae::GetSundaeDescription(void)
{
wstringstream str;
str << L"A " << GetFlavorString(m_flavor) <<
L" sundae with the following toppings: " << m_toppings.GetString();
m_description = wstring(str.str());
return m_description.c_str();
}
示例:constructors sample \ constructors sample . CPP
#include <iostream>
#include <ostream>
#include "IceCreamSundae.h"
#include "Flavor.h"
#include "Toppings.h"
#include "../pchar.h"
using namespace Desserts;
using namespace std;
typedef Desserts::Toppings::ToppingsList ToppingsList;
int _pmain(int /*argc*/, _pchar* /*argv*/[])
{
const wchar_t* outputPrefixStr = L"Current Dessert: ";
IceCreamSundae s1 = Flavor::Vanilla;
wcout << outputPrefixStr << s1.GetSundaeDescription() << endl;
s1.AddTopping(ToppingsList::HotFudge);
wcout << outputPrefixStr << s1.GetSundaeDescription() << endl;
s1.AddTopping(ToppingsList::Cherry);
wcout << outputPrefixStr << s1.GetSundaeDescription() << endl;
s1.AddTopping(ToppingsList::CrushedWalnuts);
wcout << outputPrefixStr << s1.GetSundaeDescription() << endl;
s1.AddTopping(ToppingsList::WhippedCream);
wcout << outputPrefixStr << s1.GetSundaeDescription() << endl;
s1.RemoveTopping(ToppingsList::CrushedWalnuts);
wcout << outputPrefixStr << s1.GetSundaeDescription() << endl;
wcout << endl <<
L"Copy constructing s2 from s1." << endl;
IceCreamSundae s2(s1);
wcout << endl <<
L"Copy assignment to s1 from s2." << endl;
s1 = s2;
wcout << endl <<
L"Move constructing s3 from s1." << endl;
IceCreamSundae s3(std::move(s1));
wcout << endl <<
L"Move assigning to s1 from s2." << endl;
s1 = std::move(s2);
return 0;
}