我有一些旧代码的包装。
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)最后,我可以遵循逻辑并制作一个采用non-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_);}
};
这是一个完整的示例。
#include <cstdlib>
#include <thread>
struct L {
int val;
};
void legacy_duplicate(const L* in, L** out) {
*out = new L{};
std::memcpy(*out, in, sizeof *in);
return;
}
class A {
public:
A(L* l) : impl_{l} {}
A(A const& other) : impl_{other.duplicate_locked()} {}
A copy_unsafe_for_multithreading() { return {duplicate()}; }
L* impl_;
L* duplicate() {
printf("in duplicate\n");
L* ret;
legacy_duplicate(impl_, &ret);
return ret;
}
L* duplicate_locked() const {
std::lock_guard<std::mutex> lk(mut);
printf("in duplicate_locked\n");
L* ret;
legacy_duplicate(impl_, &ret);
return ret;
}
mutable std::mutex mut;
};
int main() {
A a(new L{1});
const A b(new L{2});
A c = a;
A d = b;
A e = a.copy_unsafe_for_multithreading();
A f = const_cast<A&>(b).copy_unsafe_for_multithreading();
printf("\npointers:\na=%p\nb=%p\nc=%p\nc=%p\nd=%p\nf=%p\n\n", a.impl_,
b.impl_, c.impl_, d.impl_, e.impl_, f.impl_);
printf("vals:\na=%d\nb=%d\nc=%d\nc=%d\nd=%d\nf=%d\n", a.impl_->val,
b.impl_->val, c.impl_->val, d.impl_->val, e.impl_->val, f.impl_->val);
}
输出:
in duplicate_locked in duplicate_locked in duplicate in duplicate pointers: a=0x7f85e8c01840 b=0x7f85e8c01850 c=0x7f85e8c01860 c=0x7f85e8c01870 d=0x7f85e8c01880 f=0x7f85e8c01890 vals: a=1 b=2 c=1 c=2 d=1 f=2
[这遵循Google style guide,其中const
传达线程安全性,但是调用您的API的代码可以使用const_cast
退出)>