从Lambda返回的对象失去属性值

时间:2018-09-28 00:34:47

标签: c++ lambda

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

当在lambda中执行“返回结果”时,Contact的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;
}

2 个答案:

答案 0 :(得分:1)

Contact不会使副本构造函数过载。因此,将生成默认值。

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

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

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

这可能是有意的:

  • 有意共享资源
  • 如果Contact拥有其address的专有所有权,则无意。

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

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

address被销毁时,哪个实例负责删除Contact指针?

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

现在,销毁Contact可能会导致内存泄漏。 (外部代码必须负责删除Address的剩余实例。这很难维护。)

这种设计问题并不罕见,并导致Rule of three表示:

如果其中之一

  • 析构函数
  • 复制构造函数
  • 复制分配运算符

是显式定义的,然后是另一个。

使用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()的{​​{1}}实例的意图却适得其反。

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

Address*

在这种情况下,将“五个”设置为默认值实际上就像忽略它们一样。


我在检查时发现没有写任何愚蠢的链接:

答案 1 :(得分:1)

没有令人信服的理由在Address中使用指向Contact的指针,所以不要。这也意味着编译器生成的副本构造函数可以替换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;
}