Стратегия блокировки входящего класса в 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 включитьОптимизация времени соединения, для компоновщика должна быть возможность обойти издержки разыменованияstd::unique_ptr, Кто-нибудь проверял это?Есть ли способ позвонитьfoo_set("asdf") и есть пример ссылки правильно? Мы'возникают проблемы с выяснением, для чего предназначен правильный явный экземпляр шаблонаconst char[6], Сейчас яустановить специализацию шаблона, который создаетstd::string объект и прокси-вызов вызова foo_set (). Учитывая все обстоятельства, это, кажется, лучший путь вперед, но я хотел бы знать, как пройти "ASDF» а такжеstd::decay результат.

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

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

прочитал этоACE использует этот тип стратегии блокировки, но яЯ приветствую некоторую реальную критику со стороны пользователей 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

Ответы на вопрос(2)

Ваш ответ на вопрос