从 lambda 返回的对象将丢失属性值

Object returned from lambda loses property value

本文关键字:属性 对象 lambda 返回      更新时间:2023-10-16

我正在学习一门已有几年历史的课程,因此第一次学习如何使用序列化。

当"返回结果"在 lambda 中执行时,联系人的 Address 属性将变为未初始化。 注释掉的代码工作正常,所以我相当确定我编译了 Boost 库。

联系人上的姓名恢复正常。 为什么地址没有?

#include <string>
#include <iostream>
#include <memory>
#include <functional>
#include <sstream>
using namespace std;
#include <boost/serialization/serialization.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
struct Address
{
public:
string street, city;
int suite;
Address() {};
Address(string street, string city, int suite)
: suite(suite),street(street),city(city){ }
friend ostream& operator<<(ostream& os, const Address& obj)
{
return os
<< "street: " << obj.street
<< " city: " << obj.city
<< " suite: " << obj.suite;
}
private:
friend class boost::serialization::access;
template<class Ar> void serialize(Ar& ar, const unsigned int version)
{
ar & street;
ar & city; 
ar & suite;
}
};
struct Contact
{
string name;
Address* address;

friend ostream& operator<<(ostream& os, const Contact& obj)
{
return os
<< "name: " << obj.name
<< " address: " << *obj.address;
}
private:
friend class boost::serialization::access;
template<class Ar> void serialize(Ar& ar, const unsigned int version)
{
ar & name;
ar & address;
}
};
int main()
{
Contact john;
john.name = "John Doe";
john.address = new Address{ "123 East Dr", "London", 123 };
auto clone = [](Contact c)
{
ostringstream oss;
boost::archive::text_oarchive oa(oss);
oa << c;
string s = oss.str();
Contact result;
istringstream iss(s);
boost::archive::text_iarchive ia(iss);
ia >> result;
return result;
};
// This works fine
//ostringstream oss;
//boost::archive::text_oarchive oa(oss);
//oa << john;
//string s = oss.str();
//Contact newJane;
//{
// istringstream iss(s);
// boost::archive::text_iarchive ia(iss);
// ia >> newJane;
//}
//newJane.name = "Jane";
//newJane.address->street = "123B West Dr";
//cout << john << endl << newJane << endl;

Contact jane = clone(john);
jane.name = "Jane";
jane.address->street = "123B West Dr";
cout << john << endl << jane << endl;
getchar();
return 0;
}
Contact

不会重载复制构造函数。因此,将生成默认值。

默认复制构造函数使用其默认构造函数复制所有(非static(成员变量。具体来说,指针的默认构造函数只是复制存储的地址。

因此,使用Contact的复制构造,构造了一个新实例,其中Contact::address指向与原始实例完全相同的Address

因此,更改jane的地址也会更改joe的地址。

这可能是有意的,也可能不是:

  • 有意共享资源
  • 无意的,如果Contact对其address拥有独家所有权。

如果janejoe没有结婚,这可能是无意的。

在目前的状态下,该设计还有另一个缺陷:

哪个实例负责在销毁Contact时删除address点?

如果将其添加到析构函数中~Contact()情况就会开始变得更糟。 (删除jane会删除她的地址,并给john留下一个悬空的指针。

就像现在一样,销毁Contact可能会产生内存泄漏。(外部代码必须负责删除Address的左侧实例。这很难维护。

这样的设计问题并不罕见,并导致了三个法则,即:

如果其中之一

  • 破坏者
  • 复制构造函数
  • 复制赋值运算符

被明确定义,然后另一个很可能也是。

随着C++11(引入移动语义(,这被扩展到五法则添加

  • 移动构造函数
  • 移动分配运算符。

因此,一个明确的定义可能是简单地删除它们:

struct Contact {
Address *address;
// default constructor with initialization to empty
Contact(): address(new Address()) { }
// constructor with values
Contact(const Address &address): address(new Address(address)) { }
// destructor.
~Contact()
{
delete address; // prevent memory leak
}
// move constructor.
Contact(Contact &&contact): address(contact.address)
{
contact.address = nullptr; // prevent two owners
}
// move assignment.
Contact& operator=(Contact &&contact)
{
address = contact.address;
contact.address = nullptr; // prevent two owners
return *this;
}
// prohibited:
Contact(const Contact&) = delete;
Contact& operator=(const Contact&) = delete;
};

这是关于内存管理的改进,但在clone()Contact实例的意图方面适得其反。

另一种可能的解决方案是将address存储为std::shared_ptr<Address>而不是Address*std::shared_ptr(智能指针之一(已被引入用于此类问题(关于共享所有权(。

struct Contact {
std::shared_ptr<Address> address;
// default constructor with initialization to empty
Contact(): address(std::make_shared<Address>()) { }
// constructor with values
Contact(const Address &address):
address(std::make_shared<Address>(address))
{ }
// another constructor with values
Contact(const std::shared_ptr<Address> &address):
address(address)
{ }
// destructor.
~Contact() = default;
/* default destructor of shared_ptr is fully sufficient
* It deletes the pointee just if there are no other shared_ptr's to it.
*/
// copy constructor.
Contact(const Contact&) = default; // copies shared_ptr by default
// copy assignment.
Contact& operator=(const Contact&) = default; // copies shared_ptr by default
// move constructor.
Contact(Contact&&) = default;
// move assignment.
Contact& operator=(Contact&&) = default;
};

在这种情况下,将"五"设置为默认值实际上与将它们排除在外相同。


我在检查不要写任何愚蠢的东西时发现的链接:

  • SO:默认构造函数与隐式构造函数
  • SO:=default移动构造函数是否等同于成员移动构造函数?

没有令人信服的理由在Contact中使用指向Address的指针,所以不要。这也意味着编译器生成的复制构造函数可以替换clone

struct Contact
{
string name;
Address address;
friend ostream& operator<<(ostream& os, const Contact& obj)
{
return os
<< "name: " << obj.name
<< " address: " << obj.address;
}
private:
friend class boost::serialization::access;
template<class Ar> void serialize(Ar& ar, const unsigned int version)
{
ar & name;
ar & address;
}
};
int main()
{
Contact john;
john.name = "John Doe";
john.address = Address{ "123 East Dr", "London", 123 };
Contact jane = john;
jane.name = "Jane";
jane.address.street = "123B West Dr";
cout << john << endl << jane << endl;
getchar();
return 0;
}