0

我有以下课程:

class Data;
class A
{
public:
    A(Data& _data) : data(_data) {}
    Data& getData() {return data;}
    const Data& getData() const {return data;}
private:
    Data& data;
};

现在想象一下,我需要保留的不是一个,而是多个 Data 实例。我将它们保存在引用包装器的向量中,但我也想保持 const 的正确性:在 const 上下文中将数据作为不可修改的形式传递。

class A
{
public:
    void addData(Data& _data) {data.push_back(std::ref(_data));}
    const std::vector<std::reference_wrapper<Data>>& getData() {return data;}
    //doesn't compile
    //const std::vector<std::reference_wrapper<const Data>>& getData() const {return data;}
private:
    std::vector<std::reference_wrapper<Data>> data;
}

如何在没有物理复制数据的情况下实现这一点?即,我不想按值返回向量的副本,也不想在 A 类中保留两个单独的向量。对于基本上只是语义问题的问题,两者都是影响性能的解决方案。

4

2 回答 2

2

这是一个 const propagating reference_wrapper,基于cppreference 的可能实现

#include <utility>
#include <functional>
#include <type_traits>

namespace detail {
template <class T> T& FUN(T& t) noexcept { return t; }
template <class T> void FUN(T&&) = delete;
}

template <class T>
class reference_wrapper {
public:
  // types
  typedef T type;

  // construct/copy/destroy
  template <class U, class = decltype(
    detail::FUN<T>(std::declval<U>()),
    std::enable_if_t<!std::is_same_v<reference_wrapper, std::remove_cvref_t<U>> && !std::is_same_v<reference_wrapper<const T>, std::remove_cvref_t<U>>>()
  )>
  reference_wrapper(U&& u) noexcept(noexcept(detail::FUN<T>(std::forward<U>(u))))
    : _ptr(std::addressof(detail::FUN<T>(std::forward<U>(u)))) {}
  reference_wrapper(reference_wrapper&) noexcept = default;
  reference_wrapper(reference_wrapper&&) noexcept = default;

  // assignment
  reference_wrapper& operator=(reference_wrapper& x) noexcept = default;
  reference_wrapper& operator=(reference_wrapper&& x) noexcept = default;

  // access
  operator T& () noexcept { return *_ptr; }
  T& get() noexcept { return *_ptr; }
  operator const T& () const noexcept { return *_ptr; }
  const T& get() const noexcept { return *_ptr; }

  template< class... ArgTypes >
  std::invoke_result_t<T&, ArgTypes...>
    operator() ( ArgTypes&&... args ) {
    return std::invoke(get(), std::forward<ArgTypes>(args)...);
  }

  template< class... ArgTypes >
  std::invoke_result_t<const T&, ArgTypes...>
    operator() ( ArgTypes&&... args ) const {
    return std::invoke(get(), std::forward<ArgTypes>(args)...);
  }

private:
  T* _ptr;
};

template <class T>
class reference_wrapper<const T> {
public:
  // types
  typedef const T type;

  // construct/copy/destroy
  template <class U, class = decltype(
    detail::FUN<const T>(std::declval<U>()),
    std::enable_if_t<!std::is_same_v<reference_wrapper, std::remove_cvref_t<U>> && !std::is_same_v<reference_wrapper<T>, std::remove_cvref_t<U>>>()
  )>
  reference_wrapper(U&& u) noexcept(noexcept(detail::FUN<const T>(std::forward<U>(u))))
    : _ptr(std::addressof(detail::FUN<const T>(std::forward<U>(u)))) {}
  reference_wrapper(const reference_wrapper<T>& o) noexcept 
    : _ptr(std::addressof(o.get())) {}
  reference_wrapper(const reference_wrapper&) noexcept = default;
  reference_wrapper(reference_wrapper&&) noexcept = default;

  // assignment
  reference_wrapper& operator=(const reference_wrapper& x) noexcept = default;
  reference_wrapper& operator=(reference_wrapper&& x) noexcept = default;

  // access
  operator const T& () const noexcept { return *_ptr; }
  const T& get() const noexcept { return *_ptr; }

  template< class... ArgTypes >
  std::invoke_result_t<const T&, ArgTypes...>
    operator() ( ArgTypes&&... args ) const {
    return std::invoke(get(), std::forward<ArgTypes>(args)...);
  }

private:
  const T* _ptr;
};

// deduction guides
template<class T>
reference_wrapper(T&) -> reference_wrapper<T>;

然后,您可以通过添加 const 限定访问span

class A
{
public:
    void addData(Data& _data) {data.emplace_back(_data);}
    std::span<reference_wrapper<Data>> getData() { return { data.data(), data.size() }; }
    std::span<const reference_wrapper<Data>> getData() const { return { data.data(), data.size() }; }
private:
    std::vector<reference_wrapper<Data>> data;
}

请注意,您不能const reference_wrapper<Data>从第二个复制或移动 s getData,并且只能访问const Data &.

于 2019-09-18T15:45:34.853 回答
1

考虑访问者模式:

struct ConstVisitor {
  virtual ~ConstVisitor() = default;
  virtual bool visit(const Data & data) = 0;//returns true if search should keep going on
};

void A::accept(ConstVisitor & visitor) const;

这样,对于外界来说,数据存储在什么样的容器中(这里是 std::vector)都无关紧要。访问者模式与 C# 中的枚举器非常相似。

于 2019-09-20T07:21:39.703 回答