【问题标题】:Shared Ownership of existing ressource programming pattern现有资源编程模式的共享所有权
【发布时间】:2020-12-15 11:27:59
【问题描述】:

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

我正在开发一个表达式模板库。这有点像玩具库,我想探索的一件事是通过表达式模板使用 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*something_else 的构造函数之前使alpha 无效。
  • 如果我使用make_shared,两个表达式都将shared_ptrs 存储到alpha副本,这并不是真正的共享权限。对结果表达式求值意味着对每个 alpha 副本进行求值,从而产生冗余计算。
  • 我可以在函数体Foo 中创建一个shared_ptralpha,并将这个指针按值传递给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

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

我对此不满意的原因是我担心我真的扼杀了我希望从 ET 获得的任何性能提升。首先,我必须携带shared_ptrs 到操作数,这已经够糟糕的了在临时/范围上下文中。 ET 本来就是轻量级的。

【问题讨论】:

  • 是的,所以你排除了离开复制构造函数的点。只是通过价值。 (你为什么不考虑这个?)
  • 避免所有权问题的一些方法是将表达式模板从计算中分离出来,我的意思是CppCon 2015: Joel Falcou PART 3 “Expression Templates: Past, Present, Future”
  • @Jarod42,感谢您的链接!我不太明白您所说的“从计算中拆分表达式模板”是什么意思,但也许我看过视频后就会明白。从描述来看,这听起来正是我现在需要看的:)
  • 基本上,终端变量不存储在 AST 中,而只是一个占位符。所以没有终端变量的生命周期问题,因为不存在。
  • @Jarod42 感谢您向我介绍boost::proto/boost::yap,我想我会考虑在我的项目中使用 yap。但我仍然不知道如何解决我的问题:Yap 也没有正确处理此问题(默认情况下)godbolt.org/z/85PMT8suggestion in the yap manual 告诉我在这种情况下我应该使用std::move,但我真的不愿意从alpha 两次使用std::move,正如我的问题中提到的那样。我将如何使用占位符来实现这一点?

标签: c++ smart-pointers lifetime ownership expression-templates


【解决方案1】:

如果您传递shared_ptr bij 值,您将授予接收者对该对象的共享所有权。简单的例子

#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

【讨论】:

  • 这就是我对 的意思“我可以想出一些在 Foo 中创建 shared_ptr 的其他方法,并将其传递给 1-alpha 和 alpha*something_else 的构造函数。但这将成为库用户的负担。最好将实现细节很好地隐藏在运算符重载之后。”。但是,也许没有办法实现我想要做的事情,你的解决方案可能是最好的妥协......
  • @joergbrech 那么你应该给出一个更好的例子来说明你正在努力实现的目标以及你现在如何解决它。 “其他方式”有点宽泛……请尽可能具体。
  • 你是对的。我将编辑这个问题的要点
  • 我添加了一个代码示例,请看这里:godbolt.org/z/qn4h3q
【解决方案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::yapLive Code Example

【讨论】:

    猜你喜欢
    • 2013-08-29
    • 2016-09-03
    • 2011-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-16
    • 2013-02-15
    • 1970-01-01
    相关资源
    最近更新 更多