我可以在不分配内存或复制数据的情况下构造对象吗?

时间:2018-04-16 19:22:55

标签: c++ constructor

考虑一个班级' B'其中包含按特定顺序排列的简单char成员变量。

class B {
    char x1;
    char x2;
    char x3;
    char x4;
}

我有一个缓冲区A已经包含与B中定义的顺序相同的数据。另一个进程已经加载A数据。

char A[4];
  1. 是否可以构造包含B数据的A类型的对象,而不构造函数复制数据?也就是说,我想" overlay"将B对象放到A缓冲区上,这样我就可以对数据使用B方法,而不会产生副本的开销或内存的分配。

  2. 假设问题1有一个解决方案,我是否有任何理由不能定义一个派生自D的类B,并且有一些方法可以引用B的成员变量,但它本身不包含新成员变量?

4 个答案:

答案 0 :(得分:6)

尽管不愉快,但没有合法(标准)的方法来实现这一目标。相反,你有一个非法的,但通常是工作的(用于过多的地方)或合法的,但优化依赖。

无论方法如何,这都假设B成员没有填充(为gcc添加^{}/[\w-]+\.(?:gif|jpg|jpeg|tiff|png)$,或者编译器为B定义添加其他内容以确保没有填充)。

首先是非法的 - 您可以为该类型添加别名。这违反了严格的别名规则,但已知可以在许多平台和编译器上运行。代码示例:

[[gnu::packed]]

第二个选项是依赖编译器的优化器。它通常足够强大,可以实现不需要实际复制数据,并且只使用原始值。它不会一直发生,如果您在性能关键部分依赖此技术,则最好检查生成的代码并在每次编译器升级时重新检查它。

此代码假定优化:

const B* b = reinterpret_cast<const B*>(&a[0]);

答案 1 :(得分:6)

由于A不是B,因此没有合法的方法将A视为B。也就是说,如果A是一个标准的布局类,那么你应该能够投射它并且它将会工作&#34;工作&#34;但它不合法。例如

struct A
{
    char data[6] = "hi 0/";
    int a = 10;
    int b = 20;
};

struct B
{
    char x1;
    char x2;
    char x3;
    char x4;
    char x5;
    char x6;
};

std::ostream& operator <<(std::ostream& os, B& b)
{
    return os << b.x1 << b.x2 << b.x3 << b.x4 << b.x5 << b.x6;
}

int main()
{
    A a;
    B* b = reinterpret_cast<B*>(&a);
    std::cout << *b;
}

这是有效的,因为数组和成员在每个类中占用相同的内存部分,但这并不能保证。在x1 B之后可能存在填充,这意味着并非所有成员都将映射到数组。

现在,如果你重做B来拥有一个数组而不是像

那样的单独成员
struct A
{
    char data[6] = "hi 0/";
    int a = 10;
    int b = 20;
};

struct B
{
    char data[6];
};

然后您可以使用联盟同时拥有AB,并且由于BA具有相同common initial sequence,因此使用{{} {1}}。这可能看起来像

b

现在演员阵容已经消失,我们有guarantee from the standard这是安全的

答案 2 :(得分:-2)

是的,您可以使用现有缓冲区构建新对象。 您可以简单地使用placement new运算符:

class B
{
    char x1;
    char x2;
    char x3;
    char x4;
};

char A[4];


B *b = new(A) B; 

现在b使用A [4]的堆栈存储器; 该代码假定B打包时没有任何对齐。

class D : public B
{
public:
   void MyMethod();
};

D *d = new (A) D;

您可以在派生类中添加代码,然后使用相同的方法构建D。

以下代码:

#include <new>

#include <iostream>

struct B
{
    char x1;
    char x2;
    char x3;
    char x4;
};

char A[4] = { 'M','e','o','w' };

int main()
{
    B *b = new (A) B;

    std::cout << "The cat's " << b->x1 << b->x2 << b->x3 << b->x4 << std::endl;

    return 0;
}

输出:猫的喵

答案 3 :(得分:-2)

是的,你可以;如果您使用此处提出的新展示位置(https://isocpp.org/wiki/faq/dtors#placement-new),它将指定B的内存位置并在此处记录(https://isocpp.org/wiki/faq/dtors#placement-new),

但要注意使用它的析构函数行为,B中的初始化程序会通过覆盖数据来搞砸。您还需要非常小心内存对齐以及对象在这方面的任何要求。

该页面上需要注意的具体警告:

You are also solely responsible for destructing the placed object. This is done by explicitly calling the destructor:

ADVICE: Don’t use this “placement new” syntax unless you have to. Use it only when you really care that an object is placed at a particular location in memory. For example, when your hardware has a memory-mapped I/O timer device, and you want to place a Clock object at that memory location.

DANGER: You are taking sole responsibility that the pointer you pass to the “placement new” operator points to a region of memory that is big enough and is properly aligned for the object type that you’re creating. Neither the compiler nor the run-time system make any attempt to check whether you did this right. If your Fred class needs to be aligned on a 4 byte boundary but you supplied a location that isn’t properly aligned, you can have a serious disaster on your hands (if you don’t know what “alignment” means, please don’t use the placement new syntax). You have been warned.