为地图/矢量等实现不可复制不可移动包装器

Ama*_*tam 5 c++ generics templates stl c++14

我想在没有复制或移动构造函数的 STL 容器(如地图、向量、无序地图等)上编写一个包装器。我能想到一些方法,但没有一个是好的:

方法一:使用模板:

// Define a template NoCopyMove which can be instantiated with STL container types.
template <typename V>
struct NoCopyMove {
 public:
  using value_type = V;
  value_type& get() { return val_; }

  template <typename... Args>
  NoCopyMove(Args&&... args): val_(std::forward<Args>(args)...) {}

 private:
  NoCopyMove(const NoCopyMove&) = delete;
  NoCopyMove(NoCopyMove&&) = delete;
  value_type val_;
};

Run Code Online (Sandbox Code Playgroud)

上面可以用任何STL容器实例化,然后可以使用get()函数访问该容器

方法 2:使用公共继承:

template <typename Key,
          typename T,
          typename Hash = std::hash<Key>,
          typename KeyEqual = std::equal_to<Key>,
          class Allocator = std::allocator<std::pair<const Key, T>>>
class unordered_map_ncm
    : public std::unordered_map<Key, T, Hash, KeyEqual, Allocator> {
  // Inherit constructors.
  using std::unordered_map<Key, T, Hash, KeyEqual, Allocator>::unordered_map;

 private:
  unordered_map_ncm(const unordered_map_ncm&) = delete;
  unordered_map_ncm(unordered_map_ncm&&) = delete;
};

Run Code Online (Sandbox Code Playgroud)

上面的内容很容易受到指针切片的影响,因为 STL 容器没有虚拟构造函数,并且必须对每个 STL 类型进行虚拟构造函数。好处是我们可以使用类似 STL 的函数,而无需.get()在基于模板的方法中进行调用。

方法 2':上面的内容可以进一步推广,以便可以使用任何 STL 类型来代替映射,如下所示:

template <template <typename...> class T, typename... Us>
class NoCopyMove : public T<Us...> {
public:
    using T<Us...>::T;

private:
    NoCopyMove(const NoCopyMove&) = delete;
    NoCopyMove(NoCopyMove&&) = delete;
};

template<typename Key,
          typename T,
          typename Hash = std::hash<Key>,
          typename KeyEqual = std::equal_to<Key>,
          class Allocator = std::allocator<std::pair<const Key, T>>>
using unordered_map_ncm = NoCopyMove<std::unordered_map, Key, T, Hash, KeyEqual, Allocator>;

Run Code Online (Sandbox Code Playgroud)

即使经过这样的改进,我们仍然存在指针切片的缺点。

我想不出任何可以在没有指针切片缺点的情况下提供友好使用的东西。任何的意见都将会有帮助。

Pio*_*ycz 3

最好从容器私有继承 - 比将其作为私有成员更好 - 因为我们可以使用use Base::xxxx;它来获取类型和方法。

当然 - 删除复制构造函数和赋值运算符(移动也将被删除)。

如果我们闭上眼睛,我们无法阻止一一复制元素NoCopy<std::vector<int>> v; std::vector<int> a(v.begin(), a.end());- 那么这应该有效:

  1. 有一个类,即该模块的主类,具有一组通用的类型和方法:
namespace detail {
template <typename Base>
struct ContainerSpecific : Base {
    using Base::Base;
};
}

template <typename Container>
struct ContainerNotCopyable : private detail::ContainerSpecific<Container>
// note: this is private      ^^^^^^^  inheritance: 
{
    using Base = detail::ContainerSpecific<Container>;
    using Base::Base;

    ContainerNotCopyable(const ContainerNotCopyable&) = delete;
    ContainerNotCopyable& operator=(const ContainerNotCopyable&) = delete;

    // common stl containers types
    using Container::size_type;
    using Container::value_type;
    using Container::iterator;
    using Container::const_iterator;
    using Container::reverse_iterator;
    using Container::const_reverse_iterator;

    // common stl containers methods
    using Container::size;
    using Container::empty;
    using Container::begin;
    using Container::rbegin;
    using Container::cbegin;
    using Container::end;
    using Container::cend;
    using Container::rend;
};
Run Code Online (Sandbox Code Playgroud)
  1. 然后,对于所需的每个容器,添加特定于其的特定操作和类型:
namespace detail {
template <typename ...T>
struct ContainerSpecific<std::vector<T...>> : std::vector<T...> {
    using Base = std::vector<T...>;
    using Base::Base;

    using Base::operator[];
    using Base::at;
    using Base::erase;
    using Base::clear;
};
}

template <typename T, typename A=std::allocator<T>>
using VectorNotCopyable = ContainerNotCopyable<std::vector<T,A>>;

namespace detail {
template <typename T, std::size_t N>
struct ContainerSpecific<std::array<T, N>> : std::array<T, N> {
    using Base = std::array<T, N>;
    using Base::Base;

    using Base::operator[];
    using Base::at;
};
}

template <typename T, std::size_t N>
using ArrayNotCopyable = ContainerNotCopyable<std::array<T,N>>;

namespace detail {
template <typename ...T>
struct ContainerSpecific<std::map<T...>> : std::map<T...> {
    using Base = std::map<T...>;
    using Base::Base;
    using Base::mapped_type;
    using Base::key_type;

    using Base::find;


    using Base::emplace;
    using Base::insert;
    using Base::insert_or_assign;
    using Base::try_emplace;
};
}
template <typename K, typename V, typename C=std::less<K>, typename A=std::allocator<std::pair<const K, V>>>
using MapNotCopyable = ContainerNotCopyable<std::map<K,V,C,A>>;
Run Code Online (Sandbox Code Playgroud)

现在,我们可以检查它是否按预期工作:


template <typename T>
constexpr bool has_no_copy_move =
        !std::is_copy_constructible_v<T>
     && !std::is_copy_assignable_v<T>
     && !std::is_move_constructible_v<T>
     && !std::is_move_assignable_v<T>;

static_assert(has_no_copy_move<VectorNotCopyable<int>>);
static_assert(has_no_copy_move<ArrayNotCopyable<int, 7>>);
static_assert(has_no_copy_move<MapNotCopyable<int, int>>);


Run Code Online (Sandbox Code Playgroud)

  • 这是私有继承 - 因此任何有关封装或 IS-A 关系的错误使用的声明都不适用。您也不能通过私有继承进行对象切片,除非您在自己的方法或某些友元函数/方法中执行此操作。所有这些关于继承的担忧都是为了公共继承(也许是家庭内部受保护的继承)。 (2认同)