std ::不可移动/可复制类型的元组

时间:2018-11-19 12:42:52

标签: c++

假设我有一个结构MyStruct,其中包含一个std::mutex并且仅引用或pod成员。我想创建一个包含这种类型的std::tuple。但是由于MyStruct不可复制或移动,因此无法使用。如果我通过以下方式添加move构造函数,则可以正常工作:

#include <tuple>
#include <mutex>
#include <vector>

struct MyStruct {
        std::vector<double>& vec_;
        int number_;
        std::mutex mutex_;

        MyStruct(std::vector<double>& vec, const int number)
           : vec_(vec), number_(number) {}
        MyStruct(MyStruct&& o) : vec_(o.vec_), number_(o.number_)
        {
                std::lock_guard guard(o.mutex_);
        }
};

int main()
{
        using T = std::tuple<MyStruct,int>;

        std::vector<double> v{1., 2., 3.};
        T t{MyStruct{v, 1}, 2};
}

这样安全吗?最后,我只想在进行任何多线程操作之前在std::tuple内构造它,但是如何确保一旦std::tuple之后std::tuple的元素不会移动是建造的?还是有一种方法可以就地构建std::tuple

1 个答案:

答案 0 :(得分:1)

您的代码在特定情况下可能是安全的,但是如果您希望它总体上是安全的,则需要修复move构造函数。 move构造函数必须正确同步对从移出对象的状态的访问,并且一旦新对象窃取了该对象,它就必须防止该对象修改状态:

class MyStruct {
  std::vector<double>* vec_; // use a pointer here, not a reference
  int number_;
  std::mutex mutex_;
public:
  MyStruct(std::vector<double>& vec, const int number)
     : vec_(&vec), number_(number) {}
  MyStruct(MyStruct&& o)
  {
    std::lock_guard guard(o.mutex_);
    vec_ = std::exchange(o.vec_, nullptr);
    number_ = std::exchange(o.number_, 0); // doesn't necessarily have to be 0 here
  }
};

您还必须更改任何其他方法以说明从状态迁移。另请注意,我已将其更改为一类,并将状态设为私有。使用公共互斥锁和公共数据成员会带来麻烦。为了安全起见,必须确保所有对可变状态的访问均已正确同步。

没有Move构造函数

如果要避免使用move构造函数,那么最明显的解决方案是避免使用tupletuple适用于库中的通用代码。存在在其他情况下的有效用法,但很少见。考虑使用结构来存储值:

struct Fixture
{
  MyStruct a;
  int b;
};

这样做的优点是可以命名成员(更容易理解)并允许自定义构造函数。

使用std :: tuple

如果绝对必须使用元组,那么问题仍然可以解决。在构造元组之后,您只需要一种构造MyStruct对象的方法。有几种方法可以做到这一点。这是两个:

使用std::optional

int main()
{
  using T = std::tuple<std::optional<MyStruct>,int>;

  std::vector<double> v{1., 2., 3.};
  T t{std::nullopt, 2};

  std::get<0>(t).emplace(v, 1);
}

定义默认的构造函数并使用两阶段初始化:

// Add a constructor like the following, once again using a pointer instead of reference
MyStruct() : vec_(nullptr), number_(0) {}

// Add an init method
void init(std::vector<double>& vec, int number)
{
  vec_ = &vec;
  number_ = number;
}

// Later
int main()
{
  using T = std::tuple<MyStruct,int>;

  std::vector<double> v{1., 2., 3.};
  T t{MyStruct{}, 2};

  std::get<0>(t).init(v, 1);
}