RVO具有标准布局结构,没有任何构造函数

时间:2015-06-09 15:50:29

标签: c++ c++11 rvo

我有一个表示二进制消息的结构。我想编写一个函数来从缓冲区(无论是文件还是套接字,无关紧要)获取下一个这样的记录:

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的默认构造函数,该构造函数可能由具有非trival默认构造函数的类型组成,这些构造函数可以避免 - 这些不一定是POD类型,但它们标准布局。

有没有办法写getNext()以避免Record上的任何构造函数(默认或复制/移动)?理想情况下,当用户致电时:

auto record = getNext<Record>();

将缓冲区直接读入record的内存中。这可能吗?

2 个答案:

答案 0 :(得分:5)

no_initno_init_t类型的常量。

如果你从no_init_t构建一个pod,你会得到一个未初始化的pod,并且(假设是elision)没有什么可以做的。

如果从no_init_t构造非pod,则必须覆盖构造函数,并使其不初始化数据。通常class_name(no_init_t):field1(no_init), field2(no_init){}会这样做,有时class_name(no_init_t){}会这样做(假设所有内容都是pod)。

no_init构建每个成员可以作为一个完整性检查,但成员确实是pod。从no_init构造的非pod类将无法编译,直到您编写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)
  {}
};

现在我们可以使用Record构建no_init,但它不会被初始化。

每个POD类都未初始化。每个非POD类都必须提供no_init_t构造函数(并且可能会尽可能地实现非初始化)。

然后你memcpy就可以了。

这需要修改您的类型及其包含的类型,以支持非初始化。

答案 1 :(得分:1)

这样的东西?

编辑:

  1. 地址对齐评论。现在使用匿名联合来确保正确对齐。

  2. TestRecord现在包含另一种标准布局类型egg

  3. 添加了证明即使egg具有默认构造函数,但该类在被populateNextRecord()

  4. 填充之前未构建

    我认为这个速度和它不是一样快?

    #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;
    }