下面描述的概念如何在C++14中定义?

How the concept described below can be desined in the C++14?

本文关键字:C++14 定义 描述      更新时间:2023-10-16

我必须为自动化引擎开发一个C++框架,该框架能够连续运行任意用户操作(使用抽象方法Execute((C++类(。除了通常的输入和输出参数外,这些操作还可以访问通用数据结构,该结构充当所有操作的全局环境或上下文。我的问题是关于这个数据结构的。

上下文扮演公共存储的角色,任何操作都可以在其中放置一段数据,稍后其他一些操作可以获取此数据并使用它。因此,此上下文应充当不同类型的命名元素的容器,这些元素在设计时未知。元素可以是简单的整数、字符串或指向类的指针。它们的确切类型在运行时之前是未知的。当然,当上下文被破坏时,它应该破坏它的所有元素。

关于 std::any 的建议是有用的,但不能解决问题。 std::any 提供了一种在内部存储数据的便捷方法,但我不希望 std::any 在界面中。也就是说,我希望一个动作可以通过类似

MyType* pObj1 = new MyType(...);    // MyType is the user's class
pContext->addData("UniqueName", pObj1);

稍后的另一个操作可以通过类似

MyType* pObj1 = pContext->getData("UniqueName");

有没有办法在现代C++中实现这样的概念?

因此,在游戏开发中,这个概念通常被称为"黑板"(我确信其他域使用相同的模式 - 可能使用不同的名称(。

从本质上讲,这只是一个简单的键值映射。

很多游戏所做的就是拥有template <typename... Types> class Blackboard。黑板在内部存储一个地图元组,每种类型一个地图,将存储在黑板的给定模板实例化中。这样,库的作者不知道所有可能的存储类,但用户知道并使用他们需要的所有类型定义黑板,并且您避免了动态分配后备类型的需要。

从您的问题中,我假设您不想要这样的实现,并希望保留将存储所有可能类型的单个黑板实例。您还希望在销毁黑板实例时销毁值。

我选择做一个通用的黑板实现,它是只移动的,可以存储unique_ptr。为此,我对轮子进行了一些重新发明,并制作了一个简化版本的any(在我的实现中称为Handle(,可用于仅可移动的类型(std::any不能存储unique_ptr,因为它要求存储的类型是可复制构造的(。铌!这是一个幼稚而简单的实现,可以进行许多优化

这意味着黑板本身不负责释放任何内存,但如果用户希望黑板拥有变量,他们可以提供一个unique_ptr(甚至shared_ptr(。

C++黑板代码

class Blackboard
{
private:
struct IHandle
{
virtual ~IHandle() = default;
};
template <typename T>
struct Handle : public IHandle
{
Handle(T data)
: m_Data(std::move(data))
{
}
T m_Data;
T* get()
{
return &m_Data;
}
};
public:
template<typename T>
void AddData(const std::string& key, T object)
{
m_Map[key] = std::make_unique<Handle<T>>(Handle<T>(std::move(object)));
}
template<typename T>
T* GetData(const std::string& key)
{
auto it = m_Map.find(key);
if (it != m_Map.end())
{
if (auto* handle = dynamic_cast<Handle<T>*>(it->second.get()))
{
return handle->get();
}
}
return nullptr;
}
private:
std::map<std::string, std::unique_ptr<IHandle>> m_Map;
};

struct MyType
{
~MyType() { std::cout << "~MyType()"; }
};
int main()
{
Blackboard b;
b.AddData("someVar", 7);
int* someVar = b.GetData<int>("someVar");
std::cout << "*someVar as int: " << *someVar << std::endl;
*someVar = 88; 
b.AddData("otherVar", std::make_unique<int>(99));
std::cout << "*someVar after modifying, as int: " <<*(b.GetData<int>("someVar")) << std::endl;
std::unique_ptr<int>* otherVar = b.GetData<std::unique_ptr<int>>("otherVar");
std::cout << "*otherVar->get() as unique_ptr<int>: " << *(otherVar->get()) << std::endl;
std::cout << "otherVar as int: " << b.GetData<int>("otherVar") << std::endl;
//Blackboard blackboardCopy{ b }; //Does not compile
Blackboard movedBB{ std::move(b) }; //compiles fine, b now does not contain anything
movedBB.AddData("MyTypeVar", std::make_unique<MyType>());
//Because "MyTypeVar" is a unique_ptr<MyType>, as soon as movedBB goes out of scope
//MyType's dtor will be called
}

我的计算机上的输出:

*someVar 作为 int: 7

*通过 int* 修改后的 someVar,如 int:88

*otherVar->get(( 如 unique_ptr: 99

otherVar as int: 00000000

~我的类型((

拥有内存

如果你想让黑板成为自己的内存,很容易制作一些模板魔术,在存储指针类型之前将它们包装在unique_ptr内,并在调用getData()时返回unique_ptr.get()。出于设计原因,我个人不喜欢这种方法,因为它使 Blackboard 分担了不止一项责任。

使用std::any

您可以将我糟糕的Handle实现换成std::any.为此,无需将unique_ptr<IHandle>存储在地图中,只需存储std::any.同时将dynamic_cast换成std::any_cast。这样做的唯一缺点是您无法再在 Blackboard 中存储仅移动类型,但另一方面,您可以自由复制黑板。您还可以获得标准库为std::any所做的所有优化。