关于模仿std::vector迭代器构造的FixedVector的三个技术疑问
运行时定长连续容器的C++实现疑问解答
用户尝试用C++17实现运行时确定长度的定长数组,采用私有继承std::vector的方式,代码如下:
template <typename T> class FixedVector : private vector<T> { public: explicit FixedVector(size_t n, T t = {}) : vector<T>(n, t) {} template <typename It> FixedVector(It IteratorBegin, It IteratorEnd) : vector<T>(IteratorBegin, IteratorEnd) {} using vector<T>::begin; using vector<T>::end; using vector<T>::cbegin; using vector<T>::cend; using vector<T>::rbegin; using vector<T>::rend; using vector<T>::crbegin; using vector<T>::crend; using vector<T>::front; using vector<T>::back; using vector<T>::operator[]; using vector<T>::data; using vector<T>::at; using vector<T>::size; };
针对这段代码的三个技术疑问,解答如下:
问题1:迭代器构造函数为何合法?类模板参数T与函数模板参数It无关联,编译器为何不报错?
函数模板参数的推导是在调用阶段进行的,编译器不会在类模板定义时强制要求It和T提前绑定关联。当你实际调用这个构造函数时,编译器才会检查迭代器It指向的元素类型是否能被std::vector<T>的迭代器构造函数接受:
- 比如用
std::vector<int>的迭代器构造FixedVector<int>时,It被推导为std::vector<int>::iterator,指向元素类型int与T=int匹配,构造逻辑正常执行; - 若误用
std::vector<std::string>的迭代器构造FixedVector<int>,则std::vector<int>的构造函数会因元素类型不兼容报错,但这是调用时的类型错误,而非构造函数本身的定义错误。
简言之,模板参数的关联是在调用时通过推导和类型检查建立的,定义阶段无需提前绑定。
问题2:有没有更优雅的方式让此类拥有std::vector所有初始化时确定大小的构造函数?
可以直接使用继承构造函数(C++11及以上支持),通过using声明继承std::vector的所有构造函数,无需手动逐个编写:
template <typename T> class FixedVector : private vector<T> { public: // 继承std::vector的所有构造函数 using vector<T>::vector; // 暴露需要的成员函数,与原代码一致 using vector<T>::begin; using vector<T>::end; using vector<T>::cbegin; using vector<T>::cend; using vector<T>::rbegin; using vector<T>::rend; using vector<T>::crbegin; using vector<T>::crend; using vector<T>::front; using vector<T>::back; using vector<T>::operator[]; using vector<T>::data; using vector<T>::at; using vector<T>::size; };
这种方式不仅能自动获得std::vector中所有初始化时确定大小的构造函数(包括范围构造、初始化列表构造等),还能避免重复代码。由于是私有继承,std::vector中修改容器大小的成员函数(如push_back、resize)不会被暴露,符合定长容器的需求。
问题3:有没有更优雅的方案实现运行时确定大小、不可修改容器大小但可操作元素的连续内存容器?
推荐两种更符合封装原则的方案:
方案1:组合而非私有继承
将std::vector作为私有成员变量,彻底避免继承带来的潜在风险(如基类非虚析构、意外暴露基类接口),手动封装需要的接口:
template <typename T> class FixedVector { private: std::vector<T> vec_; public: // 转发所有构造逻辑给内部vector template <typename... Args> explicit FixedVector(Args&&... args) : vec_(std::forward<Args>(args)...) {} // 封装访问接口 auto begin() noexcept { return vec_.begin(); } auto end() noexcept { return vec_.end(); } auto cbegin() const noexcept { return vec_.cbegin(); } auto cend() const noexcept { return vec_.cend(); } T& front() { return vec_.front(); } const T& front() const { return vec_.front(); } T& back() { return vec_.back(); } const T& back() const { return vec_.back(); } T& operator[](size_t idx) { return vec_[idx]; } const T& operator[](size_t idx) const { return vec_[idx]; } T* data() noexcept { return vec_.data(); } const T* data() const noexcept { return vec_.data(); } T& at(size_t idx) { return vec_.at(idx); } const T& at(size_t idx) const { return vec_.at(idx); } size_t size() const noexcept { return vec_.size(); } };
这种方式封装性更强,逻辑更清晰,符合面向对象"组合优于继承"的设计原则。
方案2:利用std::span(C++20+)
如果不需要容器拥有内存所有权,仅需一个不可修改大小的连续内存视图,可以使用std::span。注意span是视图,不管理内存,内存需由其他容器(如std::vector)持有:
// 示例:用vector创建定长视图 std::vector<int> vec(10); std::span<int> fixed_span(vec); // fixed_span不能修改大小,但可直接操作元素 fixed_span[0] = 42;
若需要拥有内存的容器,方案1是更合适的选择。
内容的提问来源于stack exchange,提问作者Isuxiz Slidder
相关产品推荐
相关产品推荐

