我有一些旧代码的包装。
class A{
L* impl_; // the legacy object has to be in the heap, could be also unique_ptr
A(A const&) = delete;
L* duplicate(){L* ret; legacy_duplicate(impl_, &L); return ret;}
... // proper resource management here
};
在此旧版代码中,“复制”对象的函数不是线程安全的(调用相同的第一个参数时),因此const
在包装器中未对其进行标记。我猜想遵循现代规则:https : //herbsutter.com/2013/01/01/video-you-dont-know-const-and-mutable/
这duplicate
看起来是实现复制构造函数的一种好方法,除了细节不是const
。因此,我不能直接这样做:
class A{
L* impl_; // the legacy object has to be in the heap
A(A const& other) : L{other.duplicate()}{} // error calling a non-const function
L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
那么如何解决这种矛盾的情况呢?
(也可以说这legacy_duplicate
不是线程安全的,但是我知道对象退出时将其保持在原始状态。作为C函数,该行为仅记录在案,而没有常量性的概念。)
我可以想到许多可能的情况:
(1)一种可能性是根本没有办法实现具有通常语义的副本构造函数。(是的,我可以移动对象,而这不是我所需要的。)
(2)另一方面,复制对象本质上是非线程安全的,因为复制简单类型可以找到处于半修改状态的源,因此我可以继续进行此操作,
class A{
L* impl_;
A(A const& other) : L{const_cast<A&>(other).duplicate()}{} // error calling a non-const function
L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
(3)甚至只是声明duplicate
const并在所有上下文中都涉及线程安全。(毕竟,旧版功能不在乎,const
因此编译器甚至不会抱怨。)
class A{
L* impl_;
A(A const& other) : L{other.duplicate()}{}
L* duplicate() const{L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
(4)最后,我可以遵循逻辑并制作一个采用非const参数的复制构造函数。
class A{
L* impl_;
A(A const&) = delete;
A(A& other) : L{other.duplicate()}{}
L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
事实证明,这在许多情况下都有效,因为这些对象通常不是const
。
问题是,这是有效路线还是普通路线?
我无法命名它们,但是凭直觉我期望在使用非const复制构造函数的过程中会遇到很多问题。由于这种微妙之处,它可能不符合价值类型的要求。
(5)最后,尽管这似乎是一个过大的选择,并且可能会花费很高的运行时间,但我可以添加一个互斥量:
class A{
L* impl_;
A(A const& other) : L{other.duplicate_locked()}{}
L* duplicate(){
L* ret; legacy_duplicate(impl_, &ret); return ret;
}
L* duplicate_locked() const{
std::lock_guard<std::mutex> lk(mut);
L* ret; legacy_duplicate(impl_, &ret); return ret;
}
mutable std::mutex mut;
};
但是被迫这样做看起来像是悲观,使班级扩大了。我不确定。我目前倾向于(4)或(5)或两者兼而有之。
- 编辑
另外一个选项:
(6)忽略所有重复成员函数的废话,只需legacy_duplicate
从构造函数中调用并声明复制构造函数不是线程安全的。(并在必要时制作另一种类型的线程安全版本,A_mt
)
class A{
L* impl_;
A(A const& other){legacy_duplicate(other.impl_, &impl_);}
};
编辑2
这对于遗留函数的功能而言可能是一个很好的模型。请注意,通过触摸输入,相对于第一个参数表示的值,该调用不是线程安全的。
void legacy_duplicate(L* in, L** out){
*out = new L{};
char tmp = in[0];
in[0] = tmp;
std::memcpy(*out, in, sizeof *in); return;
}
legacy_duplicate
无法使用来自两个不同线程的相同第一个参数调用该函数。
const
真正含义的人之一。:-) const&
只要我不修改,我就不会三思而后行地把我的复制ctor放入other
。我一直认为线程安全是通过封装从多个线程访问任何需要添加的东西,我真的很期待答案。
L
通过创建新L
实例对其进行修改的状态?如果不是,为什么您认为此操作不是线程安全的?