c++ - 复制具有线程安全规则建议的非const参数的构造函数?

我有一些旧代码的包装。

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)最后,我可以遵循逻辑并制作一个采用非常量参数的复制构造函数。
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复制构造函数的过程中会遇到很多问题。由于这种细微差别,它可能不符合值(value)类型的要求。

(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;
};

但是被迫这样做看起来像是悲观,使类(class)扩大了。我不知道。我目前倾向于(4)(5)或两者兼而有之。

编辑1:

另外一个选项:

(6)忽略所有重复成员函数的废话,只需从构造函数调用legacy_duplicate并声明复制构造函数不是线程安全的。 (如果需要,请另外创建一个类型为A_mt的线程安全versión)
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; 
}

编辑3:
我最近了解到std::auto_ptr有一个类似的问题,即使用非const“复制”构造函数。结果是auto_ptr不能在容器内使用。 https://www.quantstart.com/articles/STL-Containers-and-Auto_ptrs-Why-They-Dont-Mix/

最佳答案

我只是同时包含了选项(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退出

https://stackoverflow.com/questions/60087792/

相关文章:

java - 如果单例不好!为什么spring bean默认是单调的

azure - 将 CloudFlare CDN 与 Azure Blob 存储结合使用

r - 从 R 中的公式中提取模型框架时排除变量

java - 使用 Java 8 Streams 将 Map 的 Map 转换为 Lists

persistence - 在 Quartz 调度程序中看到异常导致作业无法运行

python-3.x - 安装anaconda3后找不到conda命令

r - R中的双重居中

scikit-learn - 具有高斯过程的多输出空间统计

sql - 将前导零添加到 varchar 列

r - 为列名传递变量?