gpt4 book ai didi

c++ - std::launder 具有就地多态容器

转载 作者:行者123 更新时间:2023-12-02 05:33:10 26 4
gpt4 key购买 nike

我正在为 Game Boy Advance 使用 C++ 做一个有点不平凡的项目,而且,作为一个完全没有内存管理的有限平台,我试图避免调用 malloc和动态分配。为此,我实现了相当多的“就地多态容器”,它存储从 Base 派生的类型的对象。类(在类型模板中参数化),然后我有 new 的函数对象并使用完美转发来调用适当的构造函数。例如,其中一个容器如下所示(也可以访问 here ):

//--------------------------------------------------------------------------------
// PointerInterfaceContainer.hpp
//--------------------------------------------------------------------------------
// Provides a class that can effectively allocate objects derived from a
// base class and expose them as pointers from that base
//--------------------------------------------------------------------------------
#pragma once

#include <cstdint>
#include <cstddef>
#include <algorithm>
#include "type_traits.hpp"

template <typename Base, std::size_t Size>
class alignas(max_align_t) PointerInterfaceContainer
{
static_assert(std::is_default_constructible_v<Base>,
"PointerInterfaceContainer will not work without a Base that is default constructible!");
static_assert(std::has_virtual_destructor_v<Base>,
"PointerInterfaceContainer will not work properly without virtual destructors!");
static_assert(sizeof(Base) >= sizeof(std::intptr_t),
"PointerInterfaceContainer must not be smaller than a pointer");

std::byte storage[Size];

public:
PointerInterfaceContainer() { new (storage) Base(); }

template <typename Derived, typename... Ts>
void assign(Ts&&... ts)
{
static_assert(std::is_base_of_v<Base, Derived>,
"The Derived class must be derived from Base!");
static_assert(sizeof(Derived) <= Size,
"The Derived class is too big to fit in that PointerInterfaceContainer");
static_assert(!is_virtual_base_of_v<Base, Derived>,
"PointerInterfaceContainer does not work properly with virtual base classes!");

reinterpret_cast<Base*>(storage)->~Base();
new (storage) Derived(std::forward<Ts>(ts)...);
}

void clear() { assign<Base>(); }

PointerInterfaceContainer(const PointerInterfaceContainer&) = delete;
PointerInterfaceContainer(PointerInterfaceContainer&&) = delete;
PointerInterfaceContainer &operator=(PointerInterfaceContainer) = delete;

Base* operator->() { return reinterpret_cast<Base*>(storage); }
const Base* operator->() const { return reinterpret_cast<const Base*>(storage); }

Base& operator*() { return *reinterpret_cast<Base*>(storage); }
const Base& operator*() const { return *reinterpret_cast<const Base*>(storage); }

~PointerInterfaceContainer()
{
reinterpret_cast<Base*>(storage)->~Base();
}
};

阅读了一些关于 std::launder 的文章后,我仍然有疑问,但我猜这些代码行可能会导致问题:

Base* operator->() { return reinterpret_cast<Base*>(storage); }
const Base* operator->() const { return reinterpret_cast<const Base*>(storage); }

Base& operator*() { return *reinterpret_cast<Base*>(storage); }
const Base& operator*() const { return *reinterpret_cast<const Base*>(storage); }

特别是如果 Derived有问题的(或 Base 本身)有 const成员或引用文献。我要问的是关于一般准则,不仅适用于这个(和另一个)容器,还涉及 std::launder 的使用。 。看到这里你觉得怎么样?

<小时/>

因此,建议的解决方案之一是添加一个指针来接收new (storage) Derived(std::forward<Ts>(ts)...);的内容。 ,如图所示:

//--------------------------------------------------------------------------------
// PointerInterfaceContainer.hpp
//--------------------------------------------------------------------------------
// Provides a class that can effectively allocate objects derived from a
// base class and expose them as pointers from that base
//--------------------------------------------------------------------------------
#pragma once

#include <cstdint>
#include <cstddef>
#include <algorithm>
#include <utility>
#include "type_traits.hpp"

template <typename Base, std::size_t Size>
class alignas(max_align_t) PointerInterfaceContainer
{
static_assert(std::is_default_constructible_v<Base>,
"PointerInterfaceContainer will not work without a Base that is default constructible!");
static_assert(std::has_virtual_destructor_v<Base>,
"PointerInterfaceContainer will not work properly without virtual destructors!");
static_assert(sizeof(Base) >= sizeof(std::intptr_t),
"PointerInterfaceContainer must not be smaller than a pointer");

// This pointer will, in 100% of the cases, point to storage
// because the codebase won't have any Derived from which Base
// isn't the primary base class, but it needs to be there because
// casting storage to Base* is undefined behavior
Base *curObject;
std::byte storage[Size];

public:
PointerInterfaceContainer() { curObject = new (storage) Base(); }

template <typename Derived, typename... Ts>
void assign(Ts&&... ts)
{
static_assert(std::is_base_of_v<Base, Derived>,
"The Derived class must be derived from Base!");
static_assert(sizeof(Derived) <= Size,
"The Derived class is too big to fit in that PointerInterfaceContainer");
static_assert(!is_virtual_base_of_v<Base, Derived>,
"PointerInterfaceContainer does not work properly with virtual base classes!");

curObject->~Base();
curObject = new (storage) Derived(std::forward<Ts>(ts)...);
}

void clear() { assign<Base>(); }

PointerInterfaceContainer(const PointerInterfaceContainer&) = delete;
PointerInterfaceContainer(PointerInterfaceContainer&&) = delete;
PointerInterfaceContainer &operator=(PointerInterfaceContainer) = delete;

Base* operator->() { return curObject; }
const Base* operator->() const { return curObject; }

Base& operator*() { return *curObject; }
const Base& operator*() const { return *curObject; }

~PointerInterfaceContainer()
{
curObject->~Base();
}
};

但这本质上意味着 sizeof(void*) 的开销每个 PointerInterfaceContainer 字节(在相关架构中为 4)存在于代码中。这似乎不是很多,但如果我想塞满 1024 个容器,每个容器有 128 字节,这个开销就会增加。另外,它需要第二次内存访问才能访问指针,并且考虑到在 99% 的情况下,Derived将有Base作为主要基类(这意味着 static_cast<Derved*>(curObject)curObject 是同一位置),这意味着指针将始终指向 storage ,这意味着所有这些开销是完全不必要的。

最佳答案

存储std::byte对象

reinterpret_cast<Base*>(storage)

将在数组到指针衰减后指向,不能与位于该地址的任何Base对象进行指针互转换。提供存储的数组元素与其提供存储的对象之间绝不会出现这种情况。

Pointer-interconvertibility基本上仅适用于在标准布局类及其成员/基之间转换指针的情况(并且仅在特殊情况下)。这些是唯一不需要 std::launder 的情况。

因此,一般来说,对于您尝试从为对象提供存储的数组中获取指向对象的指针的用例,您总是需要应用std::reinterpret_cast 之后清洗

因此,在当前使用 reinterpret_cast 的所有情况下,您必须始终使用 std::launder。例如:

reinterpret_cast<Base*>(storage)->~Base();

应该是

std::launder(reinterpret_cast<Base*>(storage))->~Base();
<小时/>

但请注意,从 C++ 标准的角度来看,您尝试做的事情仍然不能保证有效,并且没有强制其工作的标准方法。

你的类Base需要有一个虚拟析构函数。这意味着 Base 以及从它派生的所有类都不是 standard-layout 。非标准布局的类实际上无法保证其布局。这意味着您无法保证 Derived 对象的地址等于 Base 子对象的地址,无论您如何让 Derived > 从 Base 继承。

如果地址不匹配,std::launder 将出现未定义的行为,因为在您执行 新(存储)派生

因此,您需要依赖 ABI 规范来确保 Base 子对象的地址等于 Derived 对象的地址。

关于c++ - std::launder 具有就地多态容器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60920074/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com