10

バイナリ メッセージを表す構造体があります。バッファから次のそのようなレコードを取得する関数を書きたい (ファイルかソケットかは関係ありません):

template <typename Record>
Record getNext();

今、私はこれを次のように書くことができます:

template <typename Record>
Record getNext() {
    Record r;
    populateNext(reinterpret_cast<char*>(&r),  // maybe ::read()
                 sizeof(r));                   // or equivalent
    return r;
}

これは素晴らしいことであり、RVO の利点を私に与えてくれます。ただし、のデフォルト コンストラクターがRecord呼び出されます。これは、回避したい機能を実行する非自明なデフォルト コンストラクターを持つ型で構成されている可能性があります。これらは必ずしも POD 型ではありませんが、標準レイアウトです。

getNext()コンストラクター (デフォルトまたはコピー/移動) を回避するように記述する方法はありますRecordか? 理想的には、ユーザーが呼び出すとき:

auto record = getNext<Record>();

バッファは のメモリに直接読み込まれますrecord。これは可能ですか?

4

2 に答える 2

5

no_initタイプの定数ですno_init_t

から pod を構築すると、初期化されno_init_tていない pod が得られ、(省略を前提として) 何もする必要はありません。

から非ポッドを構築する場合はno_init_t、コンストラクターをオーバーライドし、データを初期化しないようにする必要があります。通常class_name(no_init_t):field1(no_init), field2(no_init){}は実行しますが、実行することもありclass_name(no_init_t){}ます (すべてのコンテンツがポッドであると仮定)。

ただし、各メンバーから構築no_initすると、メンバーが実際にポッドであるというサニティ チェックとして機能します。から構築された非 Pod クラスは、コンストラクターno_initを記述するまでコンパイルに失敗しますno_init_t

これ (no_init各メンバー コンストラクターを使用する必要がある) は厄介な DRY エラーを生成しますが、リフレクションがないため、同じことを繰り返して気に入っていただけることでしょう。

namespace {
  struct no_init_t {
    template<class T, class=std::enable_if_t<std::is_pod<T>{}>>
    operator T()const{
      T tmp;
      return tmp;
    }
    static no_init_t instance() { return {}; }
    no_init_t(no_init_t const&) = default;
  private:
    no_init_t() = default;
  };
  static const no_init = no_init_t::instance();
}


struct Foo {
  char buff[1000];
  size_t hash;
  Foo():Foo(""){}
  template<size_t N, class=std::enable_if_t< (N<=sizeof(buff)) >>
  Foo( char const(&in)[N] ) {
    // some "expensive" copy and hash
  }
  Foo(no_init_t) {} // no initialization!
};
struct Record {
  int x;
  Foo foo;
  Record()=default;
  Record(no_init_t):
    x(no_init), foo(no_init)
  {}
};

これで構築できますが、初期化Recordno_initれません。

すべての POD クラスは初期化されていません。すべての非 POD クラスは、no_init_tコンストラクターを提供する必要があります (そして、可能な限り非初期化を実装する必要があります)。

あなたはそのmemcpy上にいます。

これには、非初期化をサポートするために、型とそれに含まれる型を変更する必要があります。

于 2015-06-09T17:29:57.160 に答える
1

このようなもの?

編集:

  1. アラインメントに関するコメントに対処します。正確なアラインメントを確保するために無名共用体を使用するようになりました。

  2. TestRecord に別の標準レイアウト タイプが組み込まれるようになりましたegg

  3. eggデフォルトのコンストラクターがあっても、クラスが作成される前にクラスが構築されないという証拠を追加しましたpopulateNextRecord()

これは可能な限り速いと思いますよね?

#include <iostream>
#include <array>
#include <algorithm>

struct egg {
    egg(int i) : _val(i) {}
    egg() {}
    int _val = 6;    
    friend std::ostream& operator<<(std::ostream& os, const egg& e) {
        return os << e._val; 
    }
};

struct TestRecord {
    egg x;
    double y;
};

void populateNext(uint8_t* first, size_t length)
{
    // do work here
    TestRecord data_source { 10, 100.2 };
    auto source = reinterpret_cast<uint8_t*>(&data_source);
    std::copy(source, source + length, first);
}

template<class Record>
struct RecordProxy
{
    RecordProxy() {}

  uint8_t* data() {
      return _data;
  }

  static constexpr size_t size() {
      return sizeof(Record);
  }

  Record& as_record() {
      return _record;
  }

    union {
        Record _record;
        uint8_t _data[sizeof(Record)];
    };
};


template <typename Record>
RecordProxy<Record> getNext() {
    RecordProxy<Record> r;
    populateNext(r.data(),  // maybe ::read()
                 r.size());                   // or equivalent
    return r;
}

using namespace std;
int main()
{
    RecordProxy<TestRecord> prove_not_initialised;
    auto& r1 = prove_not_initialised.as_record();
    cout << "x = " << r1.x << ", y = " << r1.y << endl;

    auto buffer = getNext<TestRecord>();
    auto& actual_record = buffer.as_record();
    cout << "x = " << actual_record.x << ", y = " << actual_record.y << endl;
   return 0;
}
于 2015-06-09T18:06:30.397 に答える