Стратегия блокировки входящего класса в C ++ 11

У меня есть интерфейс, использующийpimpl идиома, однако интерфейс должен быть реентерабельным. Однако вызывающим потокам не нужно знать о блокировке. Это вопрос, состоящий из четырех частей, и одна часть, безвозмездно созданная на примере C ++ 11 (пример включен для решения нескольких вопросов, подобных FAQ, которые я задаю).натыкаемся на:lockingpimplrvalue и C ++ 11, где ответы были несколько сомнительными по своему качеству).

В заголовке example.hpp:

#ifndef EXAMPLE_HPP
#define EXAMPLE_HPP

#include 
#include 

#ifndef BOOST_THREAD_SHARED_MUTEX_HPP
# include 
#endif

namespace stackoverflow {

class Example final {
public:
  typedef ::boost::shared_mutex shared_mtx_t;
  typedef ::boost::shared_lock< shared_mtx_t > shared_lock_t;
  typedef ::boost::unique_lock< shared_mtx_t > unique_lock_t;

  Example();
  Example(const std::string& initial_foo);

  ~Example();
  Example(const Example&) = delete;             // Prevent copying
  Example& operator=(const Example&) = delete;  // Prevent assignment

  // Example getter method that supports rvalues
  std::string foo() const;

  // Example setter method using perfect forwarding & move semantics. Anything
  // that's std::string-like will work as a parameter.
  template
  bool foo_set(T&& new_val);

  // Begin foo_set() variants required to deal with C types (e.g. char[],
  // char*). The rest of the foo_set() methods here are *NOT* required under
  // normal circumstances.

  // Setup a specialization for const char[] that simply forwards along a
  // std::string. This is preferred over having to explicitly instantiate a
  // bunch of const char[N] templates or possibly std::decay a char[] to a
  // char* (i.e. using a std::string as a container is a Good Thing(tm)).
  //
  // Also, without this, it is required to explicitly instantiate the required
  // variants of const char[N] someplace. For example, in example.cpp:
  //
  // template bool Example::foo_set(char const (&)[6]);
  // template bool Example::foo_set(char const (&)[7]);
  // template bool Example::foo_set(char const (&)[8]);
  // ...
  //
  // Eww. Best to just forward to wrap new_val in a std::string and proxy
  // along the call to foo_set().
  template
  bool foo_set(const char (&new_val)[N]) { return foo_set(std::string(new_val, N)); }

  // Inline function overloads to support null terminated char* && const
  // char* arguments. If there's a way to reduce this duplication with
  // templates, I'm all ears because I wasn't able to generate a templated
  // versions that didn't conflict with foo_set().
  bool foo_set(char* new_val)       { return foo_set(std::string(new_val)); }
  bool foo_set(const char* new_val) { return foo_set(std::string(new_val)); }

  // End of the foo_set() overloads.

  // Example getter method for a POD data type
  bool bar(const std::size_t len, char* dst) const;
  std::size_t bar_capacity() const;

  // Example setter that uses a unique lock to access foo()
  bool bar_set(const std::size_t len, const char* src);

  // Question #1: I can't find any harm in making Impl public because the
  // definition is opaque. Making Impl public, however, greatly helps with
  // implementing Example, which does have access to Example::Impl's
  // interface. This is also preferre, IMO, over using friend.
  class Impl;

private:
  mutable shared_mtx_t rw_mtx_;
  std::unique_ptr impl_;
};

} // namespace stackoverflow

#endif // EXAMPLE_HPP

И тогда в реализации:

#include "example.hpp"

#include 
#include 
#include 

namespace stackoverflow {

class Example;
class Example::Impl;


#if !defined(_MSC_VER) || _MSC_VER > 1600
// Congratulations!, you're using a compiler that isn't broken

// Explicitly instantiate std::string variants
template bool Example::foo_set(std::string&& src);
template bool Example::foo_set(std::string& src);
template bool Example::foo_set(const std::string& src);

// The following isn't required because of the array Example::foo_set()
// specialization, but I'm leaving it here for reference.
//
// template bool Example::foo_set(char const (&)[7]);
#else
// MSVC workaround: msvc_rage_hate() isn't ever called, but use it to
// instantiate all of the required templates.
namespace {
  void msvc_rage_hate() {
    Example e;
    const std::string a_const_str("a");
    std::string a_str("b");
    e.foo_set(a_const_str);
    e.foo_set(a_str);
    e.foo_set("c");
    e.foo_set(std::string("d"));
  }
} // anon namespace
#endif // _MSC_VER



// Example Private Implementation

class Example::Impl final {
public:
  // ctors && obj boilerplate
  Impl();
  Impl(const std::string& init_foo);
  ~Impl() = default;
  Impl(const Impl&) = delete;
  Impl& operator=(const Impl&) = delete;

  // Use a template because we don't care which Lockable concept or LockType
  // is being used, just so long as a lock is held.
  template 
  bool bar(LockType& lk, std::size_t len, char* dst) const;

  template 
  std::size_t bar_capacity(LockType& lk) const;

  // bar_set() requires a unique lock
  bool bar_set(unique_lock_t& lk, const std::size_t len, const char* src);

  template 
  std::string foo(LockType& lk) const;

  template 
  bool foo_set(unique_lock_t& lk, T&& src);

private:
  // Example datatype that supports rvalue references
  std::string foo_;

  // Example POD datatype that doesn't support rvalue
  static const std::size_t bar_capacity_ = 16;
  char bar_[bar_capacity_ + 1];
};

// Example delegating ctor
Example::Impl::Impl() : Impl("default foo value") {}

Example::Impl::Impl(const std::string& init_foo) : foo_{init_foo} {
  std::memset(bar_, 99 /* ASCII 'c' */, bar_capacity_);
  bar_[bar_capacity_] = '\0'; // null padding
}


template 
bool
Example::Impl::bar(LockType& lk, const std::size_t len, char* dst) const {
  BOOST_ASSERT(lk.owns_lock());
  if (len != bar_capacity(lk))
    return false;
  std::memcpy(dst, bar_, len);

  return true;
}


template 
std::size_t
Example::Impl::bar_capacity(LockType& lk) const {
  BOOST_ASSERT(lk.owns_lock());
  return Impl::bar_capacity_;
}


bool
Example::Impl::bar_set(unique_lock_t &lk, const std::size_t len, const char* src) {
  BOOST_ASSERT(lk.owns_lock());

  // Return false if len is bigger than bar_capacity or the values are
  // identical
  if (len > bar_capacity(lk) || foo(lk) == src)
    return false;

  // Copy src to bar_, a side effect of updating foo_ if they're different
  std::memcpy(bar_, src, std::min(len, bar_capacity(lk)));
  foo_set(lk, std::string(src, len));
  return true;
}


template 
std::string
Example::Impl::foo(LockType& lk) const {
  BOOST_ASSERT(lk.owns_lock());
  return foo_;
}


template 
bool
Example::Impl::foo_set(unique_lock_t &lk, T&& src) {
  BOOST_ASSERT(lk.owns_lock());
  if (foo_ == src) return false;
  foo_ = std::move(src);
  return true;
}


// Example Public Interface

Example::Example() : impl_(new Impl{}) {}
Example::Example(const std::string& init_foo) : impl_(new Impl{init_foo}) {}
Example::~Example() = default;

bool
Example::bar(const std::size_t len, char* dst) const {
  shared_lock_t lk(rw_mtx_);
  return impl_->bar(lk, len , dst);
}

std::size_t
Example::bar_capacity() const {
  shared_lock_t lk(rw_mtx_);
  return impl_->bar_capacity(lk);
}

bool
Example::bar_set(const std::size_t len, const char* src) {
  unique_lock_t lk(rw_mtx_);
  return impl_->bar_set(lk, len, src);
}

std::string
Example::foo() const {
  shared_lock_t lk(rw_mtx_);
  return impl_->foo(lk);
}

template
bool
Example::foo_set(T&& src) {
  unique_lock_t lk(rw_mtx_);
  return impl_->foo_set(lk, std::forward(src));
}

} // namespace stackoverflow

И мои вопросы:

Есть ли лучший способ обработки блокировки внутри частной реализации?Есть ли какой-либо вред в обнародовании Impl, если определение непрозрачное?При использовании Clang's-O4&nbsp;включитьОптимизация времени соединения, для компоновщика должна быть возможность обойти издержки разыменованияstd::unique_ptr, Кто-нибудь проверял это?Есть ли способ позвонитьfoo_set("asdf")&nbsp;и есть пример ссылки правильно? Мы'возникают проблемы с выяснением, для чего предназначен правильный явный экземпляр шаблонаconst char[6], Сейчас яустановить специализацию шаблона, который создаетstd::string&nbsp;объект и прокси-вызов вызова foo_set (). Учитывая все обстоятельства, это, кажется, лучший путь вперед, но я хотел бы знать, как пройти "ASDF»&nbsp;а такжеstd::decay&nbsp;результат.

Что касается стратегии блокировки, яМы разработали очевидный уклон к этому по нескольким причинам:

Я могу изменить мьютекс, чтобы он был эксклюзивным мьютексом, где это уместноРазработав Impl API для включения требуемой блокировки, мы получаем очень сильную гарантию семантики блокировки во время компиляцииТрудно забыть что-то заблокировать (и "простой API "&nbsp;ошибка, когда это происходит, снова компилятор поймает это, как только API будет исправлен)Трудно оставить что-то заблокированным или создать мертвую блокировку из-за RAII, и если у Impl нет ссылки на мьютексИспользование шаблонов устраняет необходимость перехода с уникальной блокировки на общую блокировку.Поскольку эта стратегия блокировки охватывает больше кода, чем требуется на самом деле, она требует явных усилий для понижения блокировки с уникальной до общей, которая обрабатывает слишком общий сценарий, когда предположения, сделанные с общей блокировкой, необходимо повторно проверить при вводе уникальной заблокированной площадьИсправления ошибок или изменения API Implне требует перекомпиляции всего приложения, так как example.hpp 's API внешне исправлен.

прочитал этоACE&nbsp;использует этот тип стратегии блокировки, но яЯ приветствую некоторую реальную критику со стороны пользователей ACE или других пользователей за повторную передачу блокировки как обязательной части интерфейса.

Ради полноты здесьs example_main.cpp для людей, чтобы пережевать.

#include 

#include 
#include 
#include 
#include 

#include "example.hpp"

int
main(const int /*argc*/, const char** /*argv*/) {
  using std::cout;
  using std::endl;
  using stackoverflow::Example;

  {
    Example e;
    cout < "Example's foo w/ empty ctor arg: " < e.foo() < endl;
  }

  {
    Example e("foo");
    cout < "Example's foo w/ ctor arg: " < e.foo() < endl;
  }

  try {
    Example e;
    { // Test assignment from std::string
      std::string str("cccccccc");
      e.foo_set(str);
      assert(e.foo() == "cccccccc");  // Value is the same
      assert(str.empty());            // Stole the contents of a_str
    }
    { // Test assignment from a const std::string
      const std::string const_str("bbbbbbb");
      e.foo_set(const_str);
      assert(const_str == "bbbbbbb");               // Value is the same
      assert(const_str.c_str() != e.foo().c_str()); // Made a copy
    }
    {
      // Test a const char[7] and a temporary std::string
      e.foo_set("foobar");
      e.foo_set(std::string("ddddd"));
    }
    { // Test char[7]
      char buf[7] = {"foobar"};
      e.foo_set(buf);
      assert(e.foo() == "foobar");
    }
    { //// And a *char[] & const *char[]
      // Use unique_ptr to automatically free buf
      std::unique_ptr