C++ 在构造时组合对象时避免数据的多个副本,而无需动态分配

C++ 在构造时组合对象时避免数据的多个副本,而无需动态分配,c++,constructor,embedded,c++17,C++,Constructor,Embedded,C++17,我们有由类表示的分层消息。它们用于通过序列化/反序列化在线程和组件之间发送消息。在我们的用例中,我们使用std::variant,但为了简化,我们的代码类似于: 类内部{ 公众: 内部(uint8_t*阵列,uint16_t阵列长度){ m_payloadLength=arrayLength;//假设arrayLength始终小于256 memcpy(m_payload.data(),数组,数组长度)); } std::阵列m_有效载荷; uint16_t m_有效负载长度; } 类外部{ 公众

我们有由类表示的分层消息。它们用于通过序列化/反序列化在线程和组件之间发送消息。在我们的用例中,我们使用
std::variant
,但为了简化,我们的代码类似于:

类内部{
公众:
内部(uint8_t*阵列,uint16_t阵列长度){
m_payloadLength=arrayLength;//假设arrayLength始终小于256
memcpy(m_payload.data(),数组,数组长度));
}
std::阵列m_有效载荷;
uint16_t m_有效负载长度;
}
类外部{
公众:
外部(常量内部和内部):m_内部(内部){};
内m_内;
}
类路由路由器{
公众:
OuterOuter(const-Outer和Outer):m_-Outer(Outer){};
外层m_外层;
}
因此,要创建一个
OuterOuter
对象,我们需要执行以下操作

int main(int argc,char**argv){
uint8_t缓冲区[4]={1,2,3,4};
内部(缓冲器,4);
外部(内部);
路由器(外部);
addToThreadQueue(路由器);
}
现在的问题是,我们使用的是嵌入式设备,所以我们不能在malloc和new中使用动态内存。到目前为止,有效负载内容是否会被复制三次?一次用于创建内部,一次用于在外部调用内部的复制构造函数,一次用于在外部路由器中调用外部的复制构造函数?如果是这样,有没有一种方法可以避免在不使用动态内存的情况下进行复制?如果有一种方法可以将我的意图传递给编译器,那么优化是可能的,如果它还没有对它进行优化的话


理想情况下,我们应该避免使用
OuterOuter
类获取所有子类构造参数,因为我们的树非常深,并且我们使用std::variant。在这个例子中,它将是
外部路由器(uint8\u t*数组,uint16\u t数组长度)
外部路由器(uint8\u t*数组,uint16\u t数组长度)
,然后
外部路由器将构建
内部路由器,现代编译器在优化类层次结构的构造方面做得很好,除了填充连续内存布局之外,这些类层次结构对它们的构造没有任何副作用

例如,gcc将示例编译为一个基本类:

main:
  sub rsp, 280
  mov eax, 4
  mov rdi, rsp
  mov WORD PTR [rsp+256], ax
  mov DWORD PTR [rsp], 67305985
  call addToThreadQueue(OuterOuter const&)
  xor eax, eax
  add rsp, 280
  ret

除此之外,编译器还可以跳过某些场景中的一些副作用。例如,在下面的示例中,gcc通过一个名为“heap elision”的进程完全摆脱了堆分配

显然,您需要重新检查自己的代码库,但通常的重复仍然是:“首先编写易于阅读和维护的代码,并且只有当编译器在这方面做得不好时,您才应该费劲地跳转来优化它。”

您可以使用inplacer(请参阅)。您的代码将显示:


Edit:类似的解决方案(但没有
inplacer
)--它不能与聚合一起工作,但我打赌在您的情况下这不是问题。

是的,有效负载被复制了三次。为了避免这种情况(同时也避免
new
malloc
),只需使用一个指向有效负载内容的指针,而不是将有效负载复制到数组成员变量中。问题是,当我们将对象附加到队列以发送到线程时,它会被复制,因为我们不知道它将在何时被处理。当指针被处理时,指针可能不是有效的。请考虑默认地构造一个<代码>外层< /代码>,然后直接将数据写入该实例的<代码>内部< /代码>。我对您的代码感到困惑。在这段代码中,为什么不使用移动语义?@N0ll\u Boy•没有可移动的资源,因此移动语义与副本没有任何不同。当编译器无法查看INTERNAR的ctor时(或当它有无法优化的副作用时),asm是什么样子的。具体地说,编译器别无选择,只能将另一个TU中定义的函数视为具有所有可能的副作用。是的,最终会导致大量内存拷贝。然而,语言有一个解决方案,允许您就地构造对象(就在您需要构造对象的地方)。虽然这是一个很好的干净的解决方案,我想使用它,但它在我的上下文中不可用,因为我不能使用堆分配,因为我们的代码在嵌入式控制器上运行。lambda
[&]{return internal{buffer,size(buffer)};
捕获当前上下文,根据编译器和上下文的大小,该上下文可能分配堆,也可能不分配堆。我们不将lambda与上下文一起使用,因为我们只想确保永远不使用堆。@XavierGroleau是的,std::函数使用堆(除非上下文足够小)Lambdas先生从来没有that@Frank谢谢!我改进了:)@Frank有另一种方法可以“修复”这个问题,但它会让MSVC不高兴…@XavierGroleau我在我的答案中添加了另一种解决方案(见底部的编辑)——你可能会发现它更适合你的情况。
#include <memory>

extern int foo(int);
extern void bar(int);

struct MyStruct {
    int data;

    MyStruct() {
        auto val = std::make_unique<int>(12); 
        data = foo(*val);
    }
};

int main(int argc, char** argv){
   MyStruct x;
   bar(x.data);
}
main:
  sub rsp, 8
  mov edi, 12
  call foo(int)
  mov edi, eax
  call bar(int)
  xor eax, eax
  add rsp, 8
  ret
#include <type_traits>
#include <array>
#include <cstdint>
#include <cstring>


using namespace std;


template<class F>
struct inplacer
{
    F f_;
    operator std::invoke_result_t<F&>() { return f_(); }
};

template<class F> inplacer(F) -> inplacer<F>;


struct Inner
{
    Inner(uint8_t* data, size_t len)
        : len_(len) // Let's assume arrayLength is always < 256
    {
        memcpy(payload_.data(), data, len*sizeof(*data));
    }

    std::array<uint8_t, 256>    payload_;
    size_t                      len_;
};

struct Outer
{
    template<class T>
    Outer(T&& inner): m_inner(std::forward<T>(inner)) {}

    Inner m_inner;
};

struct OuterOuter
{
    template<class T>
    OuterOuter(T&& outer): m_outer(std::forward<T>(outer)) {}

    Outer m_outer;
};


void addToThreadQueue(OuterOuter const&);

int main()
{
    uint8_t buffer[4]  = {1,2,3,4};
    OuterOuter outerOuter{ inplacer{[&]{ return Inner{buffer, size(buffer)}; }} };
    addToThreadQueue(outerOuter);
    return 0;
}
main:
        sub     rsp, 280
        mov     rdi, rsp
        mov     DWORD PTR [rsp], 67305985
        mov     QWORD PTR [rsp+256], 4
        call    addToThreadQueue(OuterOuter const&)
        xor     eax, eax
        add     rsp, 280
        ret