c++ - 如何在 C++14 中定义下面描述的概念?
问题描述
我必须为自动化引擎开发一个 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 as int: 7
*someVar 通过 int* 修改后,为 int: 88
*otherVar->get() as unique_ptr: 99
otherVar 作为 int: 00000000
〜我的类型()
拥有记忆
如果你想让黑板拥有自己的内存,很容易让一些模板魔术unique_ptr
在存储它们之前将指针类型包装在 a 中,并在调用unique_ptr.get()
时返回。getData()
出于设计原因,我个人不喜欢这种方法,因为它使 Blackboard 承担了不止一项责任。
使用std::any
您可以将我糟糕的Handle
实现换成std::any
.To 这样做,而不是存储unique_ptr<IHandle>
在地图中,只需存储std::any
. 也交换dynamic_cast
for std::any_cast
。这样做的唯一缺点是您不能再将仅移动类型存储在黑板上,但另一方面,您可以自由地复制黑板。您还可以获得标准库为std::any
.
推荐阅读
- reactjs - React Create App 中的 npm build 和 babel 问题
- python - _tkinter.TclError:无法连接以在绘图中显示“:0”
- timestamp - addHours,addMinutes 函数在 Azure 数据工厂中不起作用
- python - 未找到 在服务器上未找到请求的 URL。如果您手动输入了 URL,请检查您的拼写并重试。烧瓶
- c# - 怎么等到使用 selenium webdriver 重新加载部分?
- android - 如何更新 viewpager2 中附加到片段 onResume 上的片段的视图?
- java - Android JAVA:将 org.json.JSONArray 转换为 java.lang.String[]
- c++ - 当客户端断开连接时,如何使用 C++ 中的多线程服务器恢复到侦听状态?
- angular - 从Angular中的page2导航回来后如何保留page1的输入表单数据
- php - Google API 中的“其他联系人”能够在 PHP GooglePeople 中检索