假设我有一个结构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
?
答案 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构造函数,那么最明显的解决方案是避免使用tuple
。 tuple
适用于库中的通用代码。存在在其他情况下的有效用法,但很少见。考虑使用结构来存储值:
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);
}