(de) membuat serial objek c ++ hampir secara otomatis

Sep 13 2020

Berikut salah satu cara untuk menyelesaikan masalah serialisasi di c ++ 20, menggunakan pustaka json :

#include <functional>

#include <unordered_map>

#include "json.hpp"

namespace nlm = nlohmann;

class properties
{
  struct property_info
  {
    std::function<void(nlm::json const&)> deserializor;
    std::function<nlm::json()> serializor;
  };

  std::unordered_map<std::string_view, property_info> reg_;

public:    
  //
  nlm::json state() const;
  void state(nlm::json const&);

  //
  template <typename U, typename ...A>
  auto register_property(std::string_view const& k, U& v, A&& ...a)
  {
    static_assert(!(sizeof...(a) % 2));
    static_assert(!(std::is_const_v<U>));

    reg_.try_emplace(k,
      [&v](nlm::json const& j){v = j.get<U>();},
      [&v]{return nlm::json(v);}
    );

    if constexpr (sizeof...(a))
    {
      register_property(std::forward<A>(a)...);
    }

    return [this](auto&& ...a)
      {
        return register_property(std::forward<decltype(a)>(a)...);
      };
  }

  auto get(std::string_view const& k)
  {
    return reg_.find(k)->second.serializor();
  }

  template <typename U>
  void set(std::string_view const& k, U&& v)
  {
    reg_.find(k)->second.deserializor(std::forward<U>(v));
  }
};

nlm::json properties::state() const
{
  nlm::json r(nlm::json::object());

  for (auto i(reg_.cbegin()), cend(reg_.cend()); cend != i; i = std::next(i))
  {
    r.emplace(i->first, i->second.serializor());
  }

  return r;
}

void properties::state(nlm::json const& e)
{
  assert(e.is_object());
  auto const cend(reg_.cend());

  for (auto i(e.cbegin()), ecend(e.cend()); ecend != i; i = std::next(i))
  {
    auto& key(i.key());

    if (auto const j(std::as_const(reg_).find(key)); cend != j)
    {
      j->second.deserializor(i.value());
    }
  }
}

Contoh:

int main()
{
  struct S: properties
  {
    bool b{};
    int i{};

    S()
    {
      register_property("b", b)("i", i);
    }
  } s;

  s.set("b", true);
  s.set("i", 11.1);

  std::cout << s.get("b") << std::endl;
  std::cout << s.state() << std::endl;
}

2 fungsi untuk (de) serialisasi dibuat untuk setiap properti terdaftar. Jika negara diminta atau diatur, ini dijalankan sesuai. Perbaikan yang jelas adalah pemeriksaan tertentu, pengambil / penyetel, bukan referensi. Saya rasa ini adalah solusi cepat yang bagus untuk kasus sederhana.

https://wandbox.org/permlink/J0vdLOTp5x6xvF9s

Jawaban

3 G.Sliepen Sep 13 2020 at 22:19

Hindari membuat alias ruang nama di file header

Saya berasumsi bahwa setidaknya deklarasi class propertiesakan diletakkan di file header. Dalam hal ini, pertimbangkan bahwa pengguna file header itu mungkin tidak berharap namespace nlmuntuk dideklarasikan, jadi saya sarankan untuk menuliskannya nlohmannsepenuhnya.

Ejaan

Masalah kecil: serializertidak serializor.

Nama fungsi

Hindari kelebihan beban yang state()berarti mengatur negara atau mendapatkan negara bagian. Meskipun terkait, ini adalah operasi yang berbeda, dan jauh lebih baik untuk membuatnya eksplisit dengan memberi mereka nama fungsi yang berbeda. Modifikasi yang jelas adalah menamainya get_state()dan set_state(), tapi kedengarannya cukup umum. Saya juga akan menjelaskan bahwa Anda mengonversi ke atau dari JSON, jadi pertimbangkan untuk menamainya to_json()dan from_json().

Atas

Metode serialisasi Anda menimbulkan biaya overhead yang besar. Setiap instance dari struct yang dapat diserialkan sekarang harus berisi std::unordered_map, yang diisi di konstruktor. Jadi ini membutuhkan waktu dan ingatan. Akan jauh lebih baik jika Anda bisa membangun ini hanya sekali per jenis yang berasal properties. Mungkin itu bisa dilakukan dengan menggunakan variabel statis dan CRTP , seperti:

template<typename T>
struct properties
{
    struct registry
    {
        // keeps the actual mapping
        ...
    };
    
    template <typename U>
    void set(str::string_view const& k, U&& v) {
        // forward it to the registry object, along with a pointer to the object
        auto self = static_cast<T *>(this);
        self->registry.set(self, k, v);
    }

    ...
};

struct S: properties<S>
{
    bool b{};
    int i{};
    
    static properties::registry reg_;

public:
    ...
};

S::properties::registry S::reg_ = {{"b", &S::b}, {"i", &S::i}};

Tetapi saya berjuang sendiri dengan cara membuat konstruktor untuk properties::registryitu akan memungkinkan kode di atas (terutama baris terakhir) untuk bekerja.

Membuat get() const

Anda harus membuat get()fungsi anggota const, karena seharusnya tidak mengubah status, dan ini akan memungkinkan fungsi tersebut digunakan pada constinstance kelas yang mewarisi properties.

Gunakan jangkauan- forjika memungkinkan

Anda dapat menyederhanakan kode di beberapa tempat dengan menggunakan range- for. Misalnya, di, di properties::state()mana Anda juga dapat menggabungkannya dengan pengikatan terstruktur:

for (auto &[name, variable]: reg_)
{
    r.emplace(name, variable.serializer());
}

Sangat disayangkan bahwa iterator dari nlm::jsontidak bekerja dengan cara yang sama; Anda hanya dapat mengakses nilai dalam range- for, bukan kuncinya.

Crash saat runtime saat mengakses properti yang tidak ada

Jika masuk main(), Anda memanggil s.get("x"), program macet dengan kesalahan segmentasi. Bahkan jika Anda tidak pernah mengharapkan fungsi ini dipanggil dengan nama yang disediakan pengguna, itu masih menyulitkan untuk men-debug kesalahan pemrograman. Periksa nilai balik dari panggilan ke find()sebelum mencoba untuk membedakan hasilnya. Anda dapat mengajukan std::runtime_errorif find()return nulltpr, atau jika Anda tidak ingin menggunakan pengecualian atau membayar biaya performa dalam build produksi, setidaknya gunakan assert()untuk membantu build debug.

user1095108 Sep 14 2020 at 05:44

Sekarang tanpa peta:

#include <cassert>

#include <functional>

namespace nlm = nlohmann;

class properties
{
  using serializor_t = std::function<nlm::json()>;
  using deserializor_t = std::function<void(nlm::json)>;

  struct property_info
  {
    std::string_view k;
    serializor_t serializor;
    deserializor_t deserializor;
  };

  std::function<property_info const*(
    std::function<bool(property_info const&)>
  )> visitor_;

public:
  virtual ~properties() = default;

  //
  nlm::json state() const;
  void state(nlm::json const&) const;

  //
  template <std::size_t I = 0, typename A = std::array<property_info, I>, typename U>
  auto register_property(std::string_view k, U&& u, A&& a = {})
  {
    std::array<property_info, I + 1> b;
    std::move(a.begin(), a.end(), b.begin());

    if constexpr (std::is_invocable_v<U>)
    {
      *b.rbegin() = {
        std::move(k),
        [=]()noexcept(noexcept(u()))->decltype(auto){return u();},
        {}
      };
    }
    else if constexpr (std::is_lvalue_reference_v<U>)
    {
      if constexpr (std::is_const_v<std::remove_reference_t<U>>)
      {
        *b.rbegin() = {
          std::move(k),
          [&]()noexcept->decltype(auto){return u;},
          {}
        };
      }
      else
      {
        *b.rbegin() = {
          std::move(k),
          [&]()noexcept->decltype(auto){return u;},
          [&](auto&& j){u = j.template get<std::remove_cvref_t<U>>();}
        };
      }
    }

    return [this, b(std::move(b))](auto&& ...a) mutable
      {
        if constexpr (bool(sizeof...(a)))
        {
          return register_property<I + 1>(std::forward<decltype(a)>(a)...,
            std::move(b));
        }
        else
        {
          visitor_ = [b(std::move(b)), c(std::move(visitor_))](auto f)
            noexcept(noexcept(f({})))
            {
              for (auto& i: b)
              {
                if (f(i))
                {
                  return &i;
                }
              }

              return c ? c(std::move(f)) : typename A::const_pointer{};
            };
        }
      };
  }

  template <std::size_t I = 0, typename A = std::array<property_info, I>,
    typename U, typename V,
    std::enable_if_t<
      std::is_invocable_v<U> &&
      std::is_invocable_v<V, decltype(std::declval<U>()())>,
      int
    > = 0
  >
  auto register_property(std::string_view k, U&& u, V&& v, A&& a = {})
  {
    std::array<property_info, I + 1> b;
    std::move(a.begin(), a.end(), b.begin());

    *b.rbegin() = {
      std::move(k),
      [=]()noexcept(noexcept(u()))->decltype(auto){return u();},
      [=](auto&& j){v(std::forward<decltype(j)>(j));}
    };

    return [this, b(std::move(b))](auto&& ...a) mutable
      {
        if constexpr (bool(sizeof...(a)))
        {
          return register_property<I + 1>(std::forward<decltype(a)>(a)...,
            std::move(b));
        }
        else
        {
          visitor_ = [b(std::move(b)), c(std::move(visitor_))](auto f)
            noexcept(noexcept(f({})))
            {
              for (auto& i: b)
              {
                if (f(i))
                {
                  return &i;
                }
              }

              return c ? c(std::move(f)) : typename A::const_pointer{};
            };
        }
      };
  }

  //
  nlm::json get(std::string_view const&) const;

  template <typename U>
  auto set(std::string_view const& k, U&& u) const
  {
    if (auto const pi(visitor_([&](auto& pi) noexcept
      {
        return pi.k == k;
      })); pi && pi->deserializor)
    {
      pi->deserializor(std::forward<U>(u));
    }

    return [&](auto&& ...a)
      {
        return set(std::forward<decltype(a)>(a)...);
      };
  }
};

nlm::json properties::get(std::string_view const& k) const
{
  if (auto const pi(visitor_([&](auto& pi) noexcept
    {
      return pi.k == k;
    })); pi)
  {
    return pi->serializor();
  }
  else
  {
    return nlm::json();
  }
}

nlm::json properties::state() const
{
  nlm::json r(nlm::json::object());

  visitor_([&](auto& pi)
    {
      r.emplace(pi.k, pi.serializor());

      return false;
    }
  );

  return r;
}

void properties::state(nlm::json const& e) const
{
  assert(e.is_object());
  for (auto i(e.cbegin()), ecend(e.cend()); ecend != i; i = std::next(i))
  {
    auto&& k(i.key());

    if (auto const pi(visitor_([&](auto& pi) noexcept
      {
        return pi.k == k;
      })); pi && pi->deserializor)
    {
      pi->deserializor(i.value());
    }
  }
}

int main()
{
  struct S: properties
  {
    bool b{};
    int i{};

    S()
    {
      register_property("b", b)("i", i)("joke",[]{return "just a joke";})();
    }
  } s;

  s.set("b", true)("i", 11.1);

  std::cout << s.get("b") << std::endl;
  std::cout << s.state() << std::endl;
}

Ini adalah pemrograman generatif yang sedang beraksi. Kami membuat lambda untuk melintasi semua info properti. Kita bisa saja membuat struktur data (seperti array, tuple, ...), tetapi jenis ini tidak diketahui sebelumnya, jadi kita memerlukan pendekatan penghapusan tipe untuk menafsirkan dan menyimpan data ini. Ini berarti kita tidak akan dapat menghindari pembuatan functor, yang akan "mengetahui", apa struktur data yang dihasilkan dan bagaimana / di mana ia disimpan.