gpt4 book ai didi

c++ - C++初始化列表,类中的类(聚合)

转载 作者:太空宇宙 更新时间:2023-11-04 15:24:53 30 4
gpt4 key购买 nike

我正在编写洗碗机程序,洗碗机具有泵,马达和ID。泵,马达,日期,时间是洗碗机将要使用的其他小类。我与调试器进行了检查,但是在创建Dishwasher类时,未初始化我想要的值。我想我做错了什么,但是呢? :(

因此,洗碗机类如下:

class Dishwasher {
Pump pump; // the pump inside the dishwasher
Motor motor;// the motor inside the dishwasher

char* washer_id;//011220001032 means first of December 2000 at 10:32h
Time time_built;// Time variable, when the Dishwasher was built
Date date_built;// Date variable, when the Dishwasher was built

Time washing_time; // a time object, like 1:15 h
public:
Dishwasher(Pump, Motor, char*, float);
};


这就是我初始化Class的方法:

Dishwasher::Dishwasher(Pump p, Motor m, char *str, float f) 
: pump(p), motor(m), max_load(f)
{
washer_id = new char [(strlen(str)+1)];
strcpy (washer_id,str);
time_built.id2time(washer_id);
date_built.id2date(washer_id);
}


这是我创建类的方式:

Dishwasher siemens( 
Pump(160, "011219991143"),
Motor(1300, "081220031201"),
"010720081032",
17.5);


如果您想进一步了解,这是完整的代码,因为我删除了一些未使用的东西以提高可读性: http://codepad.org/K4Bocuht

最佳答案

首先,我无法克服以下事实:您正在使用char*对周围的字符串进行混排。对于大声喊叫,std::string。这是处理字符串的标准方法。它可以有效地摆脱您扔给它的任何东西。可以是字符串文字,char数组,char *或其他字符串。

其次,您的教授需要专业帮助。教导“低级直觉”是一回事,但是将其强加给应该学习C ++的学生是不明智的做法。

现在,解决眼前的问题。我仅以顶级示例为例,即位于洗碗机构造器char* str中的原始“裸”指针。如您所知,这是一个指针,即指向类型char的指针。指针存储变量的内存地址(所讨论的任何类型的变量的第一个字节,它是内存中最低的可寻址单元)。

这种明显的差异非常重要。为什么?因为当您将指针分配给其他对象时,您不是在复制实际对象,而是仅复制其第一个字节的地址。因此,实际上,您仅获得两个指向同一对象的指针。

毫无疑问,作为一个好记性的公民,您可能定义了一个析构函数来处理此问题:

washer_id = new char [(strlen(str)+1)];


您基本上是在堆上分配strlen(str)+1个字节,这是系统无法管理的,并由您掌控。因此得名,一堆。一堆东​​西,失去对它的引用,您将再也找不到它,您可以将其全部丢弃(当程序从main返回时,所有泄漏实际上发生了什么)。因此,您有责任在完成使用后告诉系统。然后,您确实定义了一个析构函数。

又大又讨厌

但是...这个方案有问题。您有一个构造函数。和一个破坏者。哪个管理资源分配和释放。但是复制呢?

Dishwasher siemens( 
Pump(160, "011219991143"),
Motor(1300, "081220031201"),
"010720081032",
17.5);


您可能知道编译器将尝试隐式创建一个基本的复制构造函数,一个复制赋值运算符(针对已构造的对象)和一个析构函数。由于隐式生成的析构函数没有任何要手动释放的内容(我们讨论了动态内存及其职责),因此它为空。

因为我们确实使用动态内存,并分配了不同大小的字节块来存储文本,所以我们有一个析构函数(如您的较长代码所示)。很好,但是我们仍然要处理一个隐式生成的复制构造函数和一个复制赋值运算符,它们复制变量的直接值。由于指针是一个变量,其值是一个内存地址,因此隐式生成的副本构造函数或副本赋值运算符所做的只是复制该内存地址(这称为浅表副本),这将创建另一个对唯一,奇异字节的引用在内存中(以及其余的连续块中)。

相反,我们想要的是深层副本,即分配新内存并在存储在传入指针的内存地址处的实际对象(或任何复合的多字节类型,数组数据结构等)上进行复制。这样,它们将指向其寿命与被包围对象的寿命相关联的不同对象,而不是从其复制对象的寿命。

在上面的示例中观察到,您正在堆栈上创建临时对象,这些临时对象在构造函数运行时有效,然后释放它们并调用其析构函数。

Dishwasher::Dishwasher(Pump p, Motor m, char *str, float f) 
: pump(p), motor(m), max_load(f)
{
washer_id = new char [(strlen(str)+1)];
strcpy (washer_id,str);
time_built.id2time(washer_id);
date_built.id2date(washer_id);
}


初始化列表还有一个好处,就是不将对象初始化为其默认值,然后执行复制,因为您很荣幸直接调用复制构造函数(在这种情况下是由编译器隐式生成的):

pump(p)基本上是调用Pump :: Pump(const Pump&)并传入临时对象已初始化的值。您的Pump类包含一个char *,其中包含第一个字节的地址,该地址是您推送到临时对象 Pump(160, "011219991143")中的字符串文字。

复制构造函数获取临时对象并复制所有显式可用的数据,这意味着它仅获取char *指针中包含的地址,而不是整个字符串。因此,您最终会从两个位置指向同一对象。

由于临时对象存在于堆栈中,因此一旦构造函数完成处理,它们将被释放并释放其析构函数。这些析构函数实际上会破坏它们以及创建Dishwasher对象时放置的字符串。现在,Dishwasher对象中的Pump对象拥有一个悬空指针,该指针指向丢失在无尽内存深渊中的对象的内存地址。

解?

编写自己的副本构造函数和副本赋值运算符重载。以Pump为例:

Pump(const Pump &pumpSrc) // copy constructor

Pump& operator=(const Pump &pumpSrc) // copy assignment operator overload


执行此 foreach类。

当然,除了析构函数之外,您已经拥有了。这三个人是基于经验法则的“三个规则”的主角。它指出,如果必须显式声明它们中的任何一个,则可能也需要显式声明其余的它们。

原则上,规则的可能部分仅仅是责任的缺失。当您深入了解C ++时,实际上需要显式定义。例如,考虑类的作用是确定是否需要所有明确定义的东西的好方法。

示例:您的类依赖于裸指针,该裸指针指向一块内存,而内存地址就是它的全部内容。这是一个简单的类型化变量,仅保留到有关对象的第一个字节的内存地址。

为了防止销毁对象时发生泄漏,您定义了一个析构函数来释放分配的内存。但是,您是否在对象之间复制数据?如您所见,很有可能。有时您将创建一个临时对象,该对象将分配数据并将内存地址存储到指针数据成员中,并将其值复制。一段时间后,该临时文件一定会被销毁,您将因此而丢失数据。您剩下的唯一内容就是带有一个无用且危险的内存地址的悬空指针。

官方免责声明:为防止我写关于此主题的书,已进行了一些简化。另外,我总是尝试着眼于当前的问题,而不是OP的代码,这意味着我不评论所采用的做法。该代码可能是可怕的,漂亮的或介于两者之间的。但是我不尝试改变代码或OP,我只是想回答这个问题,有时会提出一些我认为从长远来看可能对OP有益的东西。是的,我们可以像地狱般精确地定义所有条件...我们还可以使用Peano公理定义数字集和基本算术运算,然后大胆尝试声明2 + 3 = 3 + 2 = 5的乐趣。

关于c++ - C++初始化列表,类中的类(聚合),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10972880/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com