C++结构体中存在string类型成员相关问题

  如果你本意是想构造一个POD类型的就不要用string了。POD是Plain old data的缩写,它是一个struct或者类,且不包含构造函数、析构函数以及虚函数。 维基百科给出了更加详细的解释:C++的POD类型或者是一个标量值,或者是一个POD类型的类。POD class没有用户定义的析构函数、拷贝构造函数和非静态的非POD类型的数据成员。而且,POD class必须是一个 aggregate ,没有用户定义的构造函数,没有私有的或者保护的非静态数据,没有基类或虚函数。它只是一些字段值的集合,没有使用任何封装以及多态特性。更详细的POD介绍,请参考C++new和new()区别C++11 POD类型C++中POD类型

  对一个POD类型进行二进制拷贝后,数据成功的迁移过来了。从这个角度来看结构体中包含string后就不是一个POD类型结构了,因为影响了对象的布局。

一、结构体中含有string-使用malloc的内存分配导致的异常

 1 #include <iostream>
 2 #include <string>
 3 #include <cstdio>
 4 using namespace std;
 5 
 6 typedef struct node{
 7     string str;
 8 }NODE;
 9 
10 int main(){
11     string var = "lirao";
12     NODE * node = (NODE *)malloc (sizeof(NODE));
13     node->str = var;
14 }

  对于这个程序,在程序调试过程中,在程序的最后一行是无论如何都过不了的。出现的错误就是内存访问冲突。但是如果我们把malloc分配的内存改为new分配,NODE*node = new NODE(),问题立马就解决了,这是为什么呢?

  只是因为在NODE 结构中包含了string类型成员,用malloc分配时,由于malloc没有这样的机制,导致无法调用string的构造函数,所以无法构建起string对象,给一个不存在的对象赋值肯定错误。但是new分配内存时有这样的机制,调用了string的构造函数,所以就构建起了对象,内存访问冲突就不会发生了。

  总结:在二选一的情况下,使用new来构建,不要使用malloc函数。

二、结构体中含有string成员-使用memcpy的内存拷贝导致异常

  用#pragma pack(n)是否合适? 可以使用-但是没有必要,因为string也可以当做一个包含指针数据的机构体变量,我自己VS2013测试size_t nLen = sizeof(std::string); 其中nLen为28(定长!!!)。

  结构体内的string结构的内存长度包含了char*,函数指针等数据结构。同样的使用memcpy函数会使得 [结构体A] 和 [结构体B] 中的string内部 char* 指向相同的内存地址,在某些地方可能出错,提示double free,参考c++中包含string成员的结构体拷贝导致的double free问题

三、结构体中含有string成员-使用ifstrm.read的内存/文件读取导致异常

  这个例子很具有代表性,先贴上知乎转载的代码,问题是:  结构体Billing中有两个string类型的成员。通过函数unsigned int getCount()来读取结构体数量,return nBilling时会出现异常。而把Billing中的两个string类型全部换成char[18]类型时,return 则不会出现异常。请问为什么会出现这种情况???

 1 #include <iostream>
 2 #include <fstream>
 3 #include <string>
 4 #include <list>
 5 #include <ctime>
 6 using namespace std;
 7 
 8 struct Billing
 9 {
10     string aCardName;        //卡号
11     string aCardPwd;        //密码
12     time_t tStart;            //上机时间
13     time_t tEnd;            //下机时间
14     double dAmount;            //消费金额
15     int nStatus;            //卡状态(0-未结算;1-已结算)
16     int nDel;            //删除标志(0-未删除;1-已删除)
17 };
18 
19 //保存结构体到二进制文件billing.ams中
20 void saveBilling(Billing billing)
21 {
22     ofstream ofstrm("data/billing.ams", ofstream::out | ofstream::app | ofstream::binary);
23     if (!ofstrm)
24         cerr << "couldn't open data/billing.ams" << endl;
25     else
26     {
27         ofstrm.write(reinterpret_cast<char *>(&billing), sizeof(Billing));
28         ofstrm.flush();
29     }
30     ofstrm.close();
31 }
32 
33 //添加消费信息结构体
34 void addBilling(string cardName, string cardPwd)
35 {
36     Billing billing = {
37         cardName,
38         cardPwd,
39         time(NULL),
40         0,
41         0,
42         0,
43         0
44     };
45     saveBilling(billing);
46 }
47 
48 //读取二进制文件billing.ams中结构体数量
49 unsigned int getCount()
50 {
51     ifstream ifstrm("data/billing.ams", ifstream::in | ifstream::binary);
52 
53     unsigned int nBilling = 0;//添加的消费信息的个数
54     Billing billing;
55     while (ifstrm.read(reinterpret_cast<char *>(&billing), sizeof(Billing)))
56     {
57         cout << billing.aCardName << endl;
58         cout << billing.aCardPwd << endl;
59         nBilling++;
60     }
61     ifstrm.close();
62     return nBilling;
63 }
64 
65 
66 int main(void)
67 {
68     string s1 = "test1";
69     string s2 = "123";
70     addBilling(s1, s2);
71     s1 = "test2";
72     s2 = "456";
73     addBilling(s1, s2);
74     //-----------------------getCount()函数存在问题
75     cout << "数据数量为" << getCount() << endl;
76     //-------------------------------------------
77     system("pause");
78     return 0;
79 }
View Code

  因为std::string不是POD,不能直接以二进制形式拷贝/读写。char[n] 是固定长度数组即是 POD类型,可用以二进形式拷贝/读写。如要处理变长字符串,便需要一些序列化的机制,例如是先写入字符串的长度 n,然后写入 n 个字符。

  序列化的每个对象最终都由内置数据成员组成,如int, bool, char[]等等。处理string的序列化(serialization)步骤如下:

1 int size=aCardName.size();// store aCardName's length
2 ofstream ofstrm("data/billing.ams", ofstream::out | ofstream::app | ofstream::binary); 
3 ofstrm.write(reinterpret_cast<char *>(&size),sizeof(size));  
4 ofstrm.write(aCardName.c_str(), size+1); // write final '\0' too

  处理string 的反序列化(deserialize)步骤如下:

1 ifstream ifstrm("data/billing.ams", ifstream::in | ifstream::binary); 
2  int len=0;  
3  char *p=0;  
4   std::string aCardName; 
5   ifstrm.read(reinterpret_cast<char *>(&len), sizeof(len));  
6  p=new char [len+1]; // allocate temp buffer for name  
7   ifstrm.read(p, len+1); // copy name to temp, including '\0'  
8  aCardName=p; // copy temp to data member  
9  delete[] p;

 --------------End --------------

posted @ 2020-04-11 17:51  傍风无意  阅读(7143)  评论(0)    收藏  举报