【问题标题】:"memcopy" style construction of an object in C++C ++中对象的“memcpy”样式构造
【发布时间】:2017-02-16 00:23:27
【问题描述】:

在某些嵌入式情况下,需要使用 memcopy 样式的函数(例如从外部存储器或使用封闭的 API 调用)移动内存。

当这样一个 C++ 对象需要以这种方式移动,但它没有默认构造函数时,你不能这样做,:

class Object { 
    //local data
public:
    Object(/* not a default constructor */){}
}

//elsewhere:
Object o; //compiler will complain here...
memcpy_like_function(src_address, &o, sizeof(o));

因为Object 没有默认构造函数,因此编译器会报错创建Object o。

来自 cmets 中的一些注释:

  • memcpy_like_function 类似于 memcpy,它实际上不是 memcpy。 src_address 不是指向我可以到达的地址的指针,也不是表示指向我可以到达的地址的指针的 int。它是一个 int,表示我无法到达的内存空间中的地址。我访问这个内存空间的唯一方法就是使用这个函数。
  • Object 没有默认构造函数,没有虚函数,既不继承自,也不继承任何东西。 Objecttrivially copyable

在这种情况下创建这样一个对象而不把它放在堆上的正确方法是什么?最好,我想获得一个堆栈分配的对象,该对象将在作用域和析构函数中正常运行。对于这个问题,Object 没有继承任何东西。

【问题讨论】:

  • 请在否决和投票结束之前发表评论......否则这样做非常粗鲁。
  • @Eichhörnchen:因为它所在的内存位置在封闭的 API 调用之外是不可访问的......
  • @AndrewSpott 近距离投票将包括一个原因 - 有时不需要额外评论
  • @M.M:对不起,我认为接近投票总是需要评论。特别是考虑到这一点是“不清楚要问什么”。如果我不知道问题是什么,我无法解决它。
  • 你应该发布一些真正能代表你的情况的东西,否则你确实不清楚你在问什么。

标签: c++ heap-memory memcpy default-constructor


【解决方案1】:

这似乎是一个非常糟糕的主意,但假设您的 memcpy_like_function 确实有效,那么您只需向 Object 添加一个构造函数

class Object { 
    //local data
public:
    Object(void* src_address)
    {
        memcpy_like_function(src_address, this, sizeof(*this));
    }
};

//elsewhere:
Object o(src_address);

【讨论】:

    【解决方案2】:

    因为Object 没有默认构造函数

    Object没有默认构造函数时,

    //Object o;//NG
    

    除非调用另一个 ctor 或工厂函数,否则无法构造 Object。因此,您不能调用类似 memcpy 的函数。

    当你有办法构造Object,使用memcpy-like函数时,Object必须被授予者是trivially copyable classstandard-layout class(不等于POD class )。

    trivial class : trivially copyable class && 没有默认的用户定义构造函数
    POD class : trivial class && standard-layout class

    【讨论】:

      【解决方案3】:

      除非对象是 POD 类型,否则您不能使用类似 memcpy 的函数安全地复制对象。如果对象是 POD 类型,应该可以使用:

      char destination[sizeof(Object)];
      memcpy_like_function(src_address, destination, sizeof(destination));
      Object* ptr = reinterpret_cast<Object*>(destination);
      

      我的直觉是,这应该适用于所有 POD 类型的编译器。是否是标准某些规则下未定义行为的原因,我不确定。

      如果这是标准某些规则下未定义行为的原因,您将无法将 POD 类型保存到二进制文件并以符合标准的方式从二进制文件中读取它。它们依靠写入文件和从文件读取的位模式来表示对象。

      下面的程序在g++下产生了预期的结果。

      #include <iostream>
      #include <cstring>
      
      struct Object
      {
         int i;
         double d;
         Object(int ii, double dd) : i(ii), d(dd) {}
      };
      
      int main()
      {
         Object o1(10, 20.34);
         char dest[sizeof(Object)];
         memcpy(dest, &o1, sizeof(dest));
         Object* ptr = reinterpret_cast<Object*>(dest);
         std::cout << o1.i << ", " << o1.d << std::endl;
         std::cout << ptr->i << ", " << ptr->d << std::endl;
      }
      

      更新,以响应 OP 的 cmets

      以下程序在 g++ 下按预期工作。

      #include <iostream>
      #include <cstring>
      
      struct Object
      {
         int i;
         double d;
         Object(int ii, double dd) : i(ii), d(dd) {}
      };
      
      Object testFunction(Object o1)
      {
         char dest[sizeof(Object)];
         memcpy(dest, &o1, sizeof(dest));
         Object* ptr = reinterpret_cast<Object*>(dest);
         return *ptr;
      }
      
      int main()
      {
         Object o1(10, 20.34);
         Object o2 = testFunction(o1);
         std::cout << o1.i << ", " << o1.d << std::endl;
         std::cout << o2.i << ", " << o2.d << std::endl;
         o2.i = 25;
         o2.d = 39.65;
         std::cout << o2.i << ", " << o2.d << std::endl;
      }
      

      【讨论】:

      • 请注意,随后不允许使用Object左值访问目的地
      • 这不是一回事。这让我得到了一个具有相同数据的堆栈数组,但不是堆栈对象。
      • 这仍然是一个我无法从函数中返回的对象...尽管对问题进行编辑以明确这一点必须等待。
      • @AndrewSpott,你当然可以。只需使用return *ptr;
      • 否,因为函数返回时目的地会消失。
      【解决方案4】:

      您可以做的只是使用一个字节数组,然后进行转换。

      注意:您所做的并不是真正好的 C++ 实践,通常您应该使用赋值运算符或复制构造函数,并且您应该坚持使用更安全的 C++ 强制转换而不是暴力 C 风格的强制转换。

      无论如何,话虽如此,这段代码可以工作:

      class Object {
          int i;
      
      public:
          Object(int i) : i(i) {}
      
          void foo() const {
              std::cout << i << std::endl;
          }
      };
      
      void bla() {
          Object one(1);
          char *bytes = new char[sizeof(Object)];
          memcpy(bytes, &one, sizeof(Object));
          Object &anotherOne = (Object &) *bytes;
          anotherOne.foo();
          const Object &oneMore = (Object) *bytes;
          oneMore.foo();
          Object *oneMoreTime = (Object *) bytes;
          oneMoreTime->foo();
          delete[] bytes;
      }
      

      输出是:

      1
      1
      1
      

      总而言之,您需要在堆栈或堆上分配一个内存区域,将成为 Object 实例。

      【讨论】:

      • 注意:这是未定义的行为。 (可能会或可能不会在任何特定实现上起作用)
      • (Object) *bytes 使用第一个char 的值来调用Object 的构造函数,这可能不是本意
      • 不完全是:C 规定结构在内存中是连续分配的,因此在 C++ 中,如果数据字段是 POD,就像在这种情况下一样,那么这将起作用。但是,在对象不是 POD 的情况下,您是对的,这可能行不通。
      • 不,以Object 访问bytes' 的内容违反了严格的别名规则,而不管POD 特性如何。如果您构造了一个 Object 然后将 memcpy 的内容放入 Object 中,则后者将是相关的。
      • 这也忽略了“不把它放在堆上”的要求。除了直接访问内存,而不是通过类似 memcpy 的函数来访问。
      猜你喜欢
      • 1970-01-01
      • 2015-07-18
      • 2017-02-22
      • 2010-10-25
      • 2011-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多