C++ 工厂处理器模型中的任务所有权

C++ 工厂处理器模型中的任务所有权,c++,concurrency,C++,Concurrency,Factory异步向处理器提供不同类型的任务。处理器不知道任务的详细信息,通过已知接口执行任务。由于性能原因,禁止动态分配。工厂不应拥有任务,因为否则处理器将需要在完成任务执行时通知工厂进行清理。处理器应该只知道接口,而不知道任务本身。处理者在处理任务时可能将任务作为不透明对象 一种可能的解决方案是:将各种任务存储在“接口和填充缓冲区”的联合体中。请考虑下面的工作示例(C++ 11): #包括 结构接口 { 虚拟void execute(){} }; 联合X { X(){} 接口i; 字符填充[

Factory异步向处理器提供不同类型的任务。处理器不知道任务的详细信息,通过已知接口执行任务。由于性能原因,禁止动态分配。工厂不应拥有任务,因为否则处理器将需要在完成任务执行时通知工厂进行清理。处理器应该只知道接口,而不知道任务本身。处理者在处理任务时可能将任务作为不透明对象

一种可能的解决方案是:将各种任务存储在“接口和填充缓冲区”的联合体中。请考虑下面的工作示例(C++ 11):

#包括
结构接口
{
虚拟void execute(){}
};
联合X
{
X(){}
接口i;
字符填充[1024];
模板
X&运算符=(T&&y)
{

静态断言(sizeof(T)更新的答案。

请参阅:。它旨在与新的显式析构函数调用一起使用


以下是先前的答案,现已撤回。


从设计角度看,

  • 避免动态分配似乎是一个极端的要求,它需要一些特别的理由
  • 如果出于任何原因不信任标准分配器,仍然可以实现自定义分配器,以便完全控制其行为
  • 如果有一个类或方法“拥有”所有事物的所有实例:所有工厂、所有处理器和所有任务(就像您的
    main()
    方法一样),那么就不必复制任何东西。只需传递引用或指针,因为这个“拥有一切的类或方法”将负责对象的生存期

我的回答只适用于关于“memcpy”的问题

<>我不想涉及“接口有基类的任务,有接口的成员X”的问题。这对于所有的C++编译器来说似乎都不是普遍有效的,但是我不知道哪些C++编译器会失败这个代码。

简短答案,适用于所有C++编译器:

  • 若要在类型上使用,类型必须为
目前,普通可复制将“无虚拟函数”列为必要条件之一,因此“根据规范”的答案是,您的struct
任务
不是普通可复制的


较长的非标准答案是,特定的编译器是否将合成结构和机器代码,这将是有效的可复制的(即没有不良影响),尽管C++规范不这么说。显然,这个答案将是编译器特定的,并且将取决于很多情况。(例如优化标志和较小的代码更改)

请记住,编译器优化和代码生成可能会随着版本的变化而变化。不能保证编译器的下一个版本的行为完全相同


要举例说明在两个实例之间进行记忆可能不安全的情况,请考虑:

struct Task : public Interface
{
    Task(std::string&& s) 
        : data(std::move(s)) 
    {}
    virtual void execute() { std::cout << data << std::endl; }
    std::string data;
};

您的解决方案似乎涉及到不必要的复制操作,并对内存中的对象的布局进行假设,这些假设在所有情况下都不能保证正确。它还通过使用MeMCPY复制对象的虚拟方法,从而调用未定义的行为,这是由C++规范明确禁止的。这可能会导致混淆对象析构函数何时运行

我会使用这样的安排:

void * buffer = processor.getBuffer();
Task * task = new (buffer) Task(buffer);
processor.submitJob(task);
类处理器有一个缓冲区数组,每个缓冲区都足够大,可以包含任务接口的任何定义子类。它有两种用于提交任务的方法:

  • 返回指向当前可用缓冲区的指针
  • 提交工作的人
作业接口被扩展,要求跟踪指向包含它的缓冲区的指针(将作为构造函数参数提供),并具有返回该指针的方法

提交新任务的操作如下所示:

void * buffer = processor.getBuffer();
Task * task = new (buffer) Task(buffer);
processor.submitJob(task);

(如果需要,可以在Processor中使用模板方法简化)。然后,处理器只需执行作业,处理完作业后,它会向作业请求缓冲区,运行其析构函数,并将缓冲区添加回其可用缓冲区列表。

请参阅:。它设计为与新的显式析构函数调用一起使用。事实上,对象无需简单地可复制即可复制多次(最后,std::vector可以存储它们)。因为浅拷贝已经足够了,而且是首选!如果右值
y
将破坏其数据,则会出现问题。我检查了上面的示例,仅在
t
上调用析构函数,而不是
y
(当然,
y
是一个参考,但必须有相应的临时文件)!为确保数据安全,
y
可能在
memcpy
完成后被剥夺数据所有权。@midenok在您的代码示例中,它不使用memcpy在
Task
的两个实例之间进行复制。它从一个
Task
复制到一个字节数组中。执行该复制的函数实际上对
y
,因此它不会调用
y
的析构函数。在您的实验中,
t
的析构函数实际上是在
main()
方法(范围)的末尾调用的,这是范围绑定对象销毁的一个示例。当然,
y
是一个引用,并且在引用时不调用析构函数。但是
std::move
应该创建一个临时的…例如,复制语义(
ty
签名而不是
T&&y
)导致
y.~Task()
调用。这一定是因为我缺乏移动语义方面的知识……因此,如果移动语义设计为不破坏临时性,那么一切都是好的!传入
(ty)
void * buffer = processor.getBuffer();
Task * task = new (buffer) Task(buffer);
processor.submitJob(task);