【问题标题】:Variable-size object in shared_ptrshared_ptr 中的可变大小对象
【发布时间】:2015-04-04 23:14:02
【问题描述】:

假设我想要一个可变大小的数组,前面有一点头存储在std::shared_ptr 中。我可以做类似的事情

#include <memory>
using namespace std;
struct obj {
  char headerCode;
  unique_ptr<short[]> data;
};
shared_ptr<obj> make(unsigned len) {
  return shared_ptr<obj>{new obj{'x', unique_ptr<short[]>{new short[len]}}};
}

但这会导致三种分配:一种用于shared_ptr 控制块,一种用于obj,另一种用于其data。对于make_shared,前两个可能共享一些内存:

#include <memory>
using namespace std;
struct obj {
  char headerCode;
  unique_ptr<short[]> data;
  obj(char headerCode, short data[]) : headerCode(headerCode), data(data) {}
};
shared_ptr<obj> make(unsigned len) {
  return make_shared<obj>('x', new short[len]);
}

通过低级分配,我可以使对象及其数据共享一些内存:

#include <memory>
#include <cstdlib>
using namespace std;
struct obj {
  char headerCode;
  short data[0];
};
shared_ptr<obj> make(unsigned len) {
  obj* o = reinterpret_cast<obj*>(malloc(sizeof(obj) + len*sizeof(short)));
  o->headerCode = 'x';
  return shared_ptr<obj>(o, free);
}

标准是否允许这两种技术?如果没有,是否有类似的东西是允许的?有没有什么东西可以使这项工作以符合标准的方式工作,只需要一个内存分配?最好不必在每个实例中都存储分配器或删除器对象?

【问题讨论】:

  • 为什么要使用reinterpret_castvoid* 转换为obj*
  • @JonathanWakely:没有明确转换我的g++error: invalid conversion from ‘void*’ to ‘obj*’ [-fpermissive]。但你是对的,static_cast 在这里可能更合适。
  • 是的,当然不会隐式转换,但是reinterpret_cast是个大锤,这里没必要。 static_cast 就足够了。

标签: c++ arrays memory-management smart-pointers


【解决方案1】:

标准是否允许这两种技术?

第一个很好,第二个使用零长度数组作为成员 (data[0]),它不是有效的 C++,但被一些编译器支持作为扩展。

有什么东西可以使这项工作以符合标准的方式工作,只分配一次内存?

目前,如果没有 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3641.html,这将非常困难,因为它允许这样的事情:

struct obj {
  char headerCode;
  short* data;
};

shared_ptr<obj> make(unsigned len) {
  auto p = make_shared<char[]>(sizeof(obj) + sizeof(short)*len));
  short* s = static_cast<short*>(p.get() + sizeof(obj));
  return shared_ptr<obj>(p, ::new(p.get()) obj{'x', s});
}

这会为objshort 数组分配足够空间的char 数组,然后使用placement new 将obj 构造到该内存中,并创建另一个shared_ptr 共享所有权拥有char 数组的那个。当最后一个拥有内存的shared_ptr 删除它的引用时,char 数组将被正确删除,但obj 对象的析构函数将不会运行,因此不要依赖它的析构函数有任何一面,这一点很重要效果(理想情况下它会有一个微不足道的析构函数)。

您今天也许可以使用allocate_shared 和自定义分配器来为short 的数组分配额外空间,但由于您不知道实现将如何安排控制块和您的对象分配的空间很难计算出short数组从哪里开始,分配器需要存储在控制块中。

【讨论】:

  • 即使short 不太可能导致问题,也不要忘记考虑对齐。一般来说,不能保证p.get() + sizeof(obj) 与您想在其后放置的任何内容适当对齐,尽管与short 相比,几乎所有平台都会使指针具有同样严格或更严格的对齐要求。
  • 是的,我确实考虑过,但在这种情况下它应该是安全的,因为尾随指针成员应该对齐,以便short 可以立即跟随它。通常,您可能需要在分配中添加(最多)alignof(T) 字节并使用std::align 正确放置T 的数组。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-21
相关资源
最近更新 更多