从线程安全性规则建议的非const参数中复制构造函数?

问题描述 投票:2回答:1

我有一些旧代码的包装。

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_);} };
c++ c++11 thread-safety legacy const-correctness
1个回答
0
投票
我只是同时包含了选项(4)和(5),但是当您认为有必要提高性能时,明确选择加入线程不安全的行为。

这是一个完整的示例。

#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退出)>
© www.soinside.com 2019 - 2024. All rights reserved.