现有资源编程模式的共享所有权



我知道关于现有资源共享所有权的问题以前已经问过很多次了,但不知何故,我未能找到我的具体问题的答案。如果我错了,请纠正我。

我正在开发一个表达式模板库。这有点像玩具库,我想探索的一件事是通过表达式模板使用C++类型对算法进行编码的优缺点。例如,这可以用于惰性计算和算法微分。为此,库的用户应该能够修改现有的代码块,以便它通过运算符重载在后台使用我的库的表达式模板。

现在我不确定如何处理子表达式的所有权。例如,请考虑以下功能:

auto Foo() (
auto alpha = [...]; // some calculation, result is an expression, e.g. Div<Sub<Value,Value>,Sub<Value,Value>>
return (1-alpha)*something + alpha*something_else;
)

此函数创建类型的新表达式

Add<Mult<Sub<Value,TYPE_OF_ALPHA>,TYPE_OF_SOMETHING>, Mult<TYPE_OF_ALPHA, TYPE_OF_SOMETHING_ELSE>>

很明显,代表1-alphaalpha*something_else的表达式应该共享alpha所有权,因为当我们退出Fooalpha将超出范围。这告诉我,我应该使用shared_ptr成员来表达式中的子表达式。- 或者这已经是一种误解?

我的问题

我将如何编写二进制操作表达式的构造函数SubMult,以便表达式真正占据传递给构造函数的对象/子表达式/操作数的共享所有权 - 以这样一种方式,用户必须对函数所做的更改保持最小Foo

  • 我不想将对象移动到1-alpha,因为这会使alpha在我调用alpha*something_else的构造函数之前失效。
  • 如果我使用make_shared,两个表达式都存储shared_ptrs 到alpha的副本,这不是真正的共享。计算生成的表达式意味着计算 alpha 的每个副本,从而产生冗余计算。
  • 我可以创建一个shared_ptralpha函数体Foo,并按值将此指针传递给1-alphaalpha*something_else的构造函数。但这对图书馆的用户来说将是一个负担。最好将实现细节很好地隐藏在运算符重载后面:理想情况下,用户只需对其现有Foo函数进行最少的更改即可将其与表达式模板库一起使用。我在这里要求太多了吗?
<小时 />

编辑1:

下面是一个示例,我使用第二个选项,在构造函数中创建副本(我认为...):https://godbolt.org/z/qn4h3q

<小时 />

编辑2:

我找到了一个有效的解决方案,但我不愿意回答我自己的问题,因为我对我的解决方案不是 100% 满意。

如果我们想获得自定义类资源的共享所有权,我们可以修改(在我的例子中),我们可以为该类配备一个shared_ptr到它自己的副本。只要没有人拥有对象的所有权,shared_ptr就会nullptr

#include <memory>
#include <iostream>
class Expression
{
public:
Expression(std::string const& n)
: name(n) 
{}
~Expression(){
std::cout<<"Destroy "<<name<<std::endl;
}
std::shared_ptr<Expression> shared_copy()
{
return shared_ptr? shared_ptr : shared_ptr = std::make_shared<Expression>(*this);
}
std::string name;
std::shared_ptr<Expression> subexpression;
private:
std::shared_ptr<Expression> shared_ptr = nullptr;
};

int main()
{
// both a and b shall share ownership of the temporary c
Expression a("a");
Expression b("b");
{
Expression c("c");
a.subexpression = c.shared_copy();
b.subexpression = c.shared_copy();
// rename copy of c now shared between a and b
a.subexpression->name = "c shared copy";
}

std::cout<<"a.subexpression->name = "<<a.subexpression->name<<std::endl;
std::cout<<"b.subexpression->name = "<<b.subexpression->name<<std::endl;
std::cout<<"b.subexpression.use_count() = "<<b.subexpression.use_count()<<std::endl;
}

输出:

Destroy c
a.subexpression->name = c shared copy
b.subexpression->name = c shared copy
b.subexpression.use_count() = 2
Destroy b
Destroy a
Destroy c shared copy

实时代码示例:https://godbolt.org/z/xP45q6。

以下是使用实际表达式模板对编辑 1中给出的代码示例的改编:https://godbolt.org/z/86YGfe。

我之所以对此不满意,是因为我担心我真的扼杀了我希望从 ET 中获得的任何性能提升。首先,我必须随身携带shared_ptrs到操作数,这已经够糟糕的了,其次,表达式必须随身携带一个额外的shared_ptr,只是为了怪异的情况,它将用于临时/范围上下文。ET应该是轻量级的。

如果传递shared_ptrbij 值,则向接收方提供对象的共享所有权。简单示例

#include <memory>
struct Sub{
std::shared_ptr<int> a,b;
};
#include <cstdio>
int main() {
auto s = Sub{};
{
auto v = std::make_shared<int>(5);
s = Sub{v,v};
}
printf("reference count %ld", s.a.use_count());
}

返回:reference count 2

事实证明,对于我的用例,我可以完全避免共享所有权。大多数表达式模板库都提供一个EVALUTION_EXPRESSION,它表示函数的计算,给定一些可能是表达式的参数。在计算EVALUATION_EXPRESSION时,首先计算参数,然后将其结果传递到函数中。

使用它,我可以定义一个linear_combine函数并将其包装在一个EVALUATION_EXPRESSION中,该应该是alpha的唯一所有者:

template <typename Scalar, typename Vector>
Vector linear_combine(Scalar alpha, Vector const& x, Vector const& y){
return (1.-alpha)*x + alpha*y;
}
auto Foo() (
auto alpha = [...]; // some calculation, result is an expression, e.g. Div<Sub<Value,Value>,Sub<Value,Value>>
return EVALUTION_EXPRESSION(linear_combine)(std::move(alpha), something, something_else);
)

下面是使用表达式模板库 boost::yap 的实时代码示例。

相关内容

  • 没有找到相关文章

最新更新