Объект, возвращаемый из лямбды, теряет значение свойства - PullRequest
0 голосов
/ 28 сентября 2018

Я прохожу курс обучения, которому уже несколько лет, поэтому учусь впервые использовать сериализацию.

Когда в лямбде выполняется «возвращаемый результат», свойство Address в Contact становится неинициализированным.Закомментированный код прекрасно работает, поэтому я уверен, что скомпилировал библиотеки 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 голосов
/ 28 сентября 2018

Нет веской причины использовать указатели на 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;
}
0 голосов
/ 28 сентября 2018

Contact не перегружает конструктор копирования.Следовательно, генерируется значение по умолчанию.

Конструктор копирования по умолчанию копирует все (не static) переменные-члены, используя их конструкторы по умолчанию.В частности, конструктор указателя по умолчанию просто копирует адрес, который хранится.

Итак, используя конструкцию копирования Contact, создается новый экземпляр с Contact::address, указывающим на точно такой же Address как оригинальный экземпляр.

Следовательно, изменение адреса jane также изменит адрес joe.

Это может быть предназначено или нет:

  • преднамеренно дляподелиться ресурсами
  • непреднамеренно, если Contact будет иметь исключительное право собственности на address.

Если jane и joe не состоят в браке, это может быть непреднамеренным.

В текущем состоянии у проекта есть еще один недостаток:

Какой экземпляр отвечает за удаление пуантина address при уничтожении Contact?

Если это будетдобавлено в деструктор ~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;
};

Установка «пятерки»по умолчанию в этом случае фактически то же самое, что и пропуск их.


Ссылки, которые я обнаружил при проверке, чтобы не писать ничего глупого:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...