如何避免Vector与VectorView类的代码冗余并实现兼容的运算操作?
如何避免Vector与VectorView类的代码冗余并实现兼容的运算操作?
这个问题我之前做几何工具库时也踩过坑——重复写一堆几乎一模一样的运算符和方法,不仅繁琐还容易出错!其实咱们可以用几个C++模板技巧,彻底解决代码冗余,同时让Vector和VectorView的运算无缝兼容。
第一步:用CRTP提取公共逻辑
核心思路是把两个类的公共操作(比如operator+=、点积、标量运算等)抽出来,放到一个基于CRTP(奇异递归模板模式)的基类里。基类通过子类提供的data()方法统一访问数据,不用关心子类是自己持有数组还是指向外部指针。
先写基类:
template <typename Derived, typename T, std::size_t N> class VectorBase { public: // 统一数据访问接口,由子类实现 T* data() { return static_cast<Derived*>(this)->data(); } const T* data() const { return static_cast<const Derived*>(this)->data(); } // 公共的operator+=实现,一次编写,子类共享 Derived& operator+=(const VectorBase& other) { for (std::size_t i = 0; i < N; ++i) { data()[i] += other.data()[i]; } return *static_cast<Derived*>(this); } // 点积运算同样只写一次 T dot(const VectorBase& other) const { T result = T{}; for (std::size_t i = 0; i < N; ++i) { result += data()[i] * other.data()[i]; } return result; } // 其他公共操作,比如operator-=、标量乘法等都可以在这里实现 Derived& operator*=(const T& scalar) { for (std::size_t i = 0; i < N; ++i) { data()[i] *= scalar; } return *static_cast<Derived*>(this); } };
然后让Vector和VectorView继承这个基类,只需要实现自己特有的逻辑(比如构造、数据持有方式):
// 持有数据的Vector类 template <typename T, std::size_t N> class Vector : public VectorBase<Vector<T, N>, T, N> { private: T m_data[N]; public: Vector() = default; Vector(const T values[N]) { for (std::size_t i = 0; i < N; ++i) { m_data[i] = values[i]; } } // 实现基类要求的data()接口 T* data() { return m_data; } const T* data() const { return m_data; } // Vector特有的拷贝/赋值逻辑 Vector(const Vector&) = default; Vector& operator=(const Vector&) = default; }; // 不持有数据的VectorView类 template <typename T, std::size_t N> class VectorView : public VectorBase<VectorView<T, N>, T, N> { private: T* m_data; public: VectorView() = default; VectorView(T* data) : m_data(data) {} // 实现基类要求的data()接口 T* data() { return m_data; } const T* data() const { return m_data; } // 可以添加View特有的校验逻辑,比如空指针检查 bool is_valid() const { return m_data != nullptr; } };
第二步:用通用模板实现自由运算符
对于operator+、operator*这类非成员运算符,不用写4种重载(Vector+Vector、Vector+VectorView等),而是写一个通用模板函数,自动兼容所有VectorBase的派生类:
// 通用的vector+vector运算 template <typename Derived1, typename Derived2, typename T, std::size_t N> auto operator+(const VectorBase<Derived1, T, N>& lhs, const VectorBase<Derived2, T, N>& rhs) { // 结果类型优先选持有数据的Vector(避免返回悬空的View) using ResultType = std::conditional_t< std::is_same_v<Derived1, Vector<T, N>> || std::is_same_v<Derived2, Vector<T, N>>, Vector<T, N>, Vector<T, N> // 如果两个都是View,也返回Vector,避免内存风险 >; ResultType result; for (std::size_t i = 0; i < N; ++i) { result.data()[i] = lhs.data()[i] + rhs.data()[i]; } return result; } // 通用的标量乘法 template <typename Derived, typename T, std::size_t N> auto operator*(const VectorBase<Derived, T, N>& vec, const T& scalar) { Derived result = static_cast<const Derived&>(vec); result *= scalar; return result; } template <typename Derived, typename T, std::size_t N> auto operator*(const T& scalar, const VectorBase<Derived, T, N>& vec) { return vec * scalar; }
如果是C++20及以上,还可以用**概念(Concepts)**约束模板参数,确保传入的类型符合Vector的要求,让代码更安全:
template <typename T> concept VectorLike = requires(T v) { typename T::value_type; { v.data() } -> std::same_as<typename T::value_type*>; { v.data() const } -> std::same_as<const typename T::value_type*>; }; // 用Concept约束后的operator+ template <VectorLike Vec1, VectorLike Vec2> requires (std::same_as<typename Vec1::value_type, typename Vec2::value_type> && Vec1::size == Vec2::size) auto operator+(const Vec1& lhs, const Vec2& rhs) { // 实现逻辑同上 }
最终效果
现在你原来的使用场景完全能正常工作,而且不用重复写任何冗余代码:
Vector<float, 3> ratio {10,10,0}; std::vector<float> image(32*32*3); VectorView<float, 3> pixel(&image[10*3]); pixel += ratio; // 直接调用基类的operator+=,修改image的内存 auto sum = ratio + pixel; // 返回Vector<float,3>,自动处理类型 auto scaled_pixel = pixel * 2.0f; // 返回VectorView<float,3>,直接修改原数据
这种设计的好处:
- 公共逻辑只写一次,后续扩展新操作(比如叉积、矩阵变换)时,只需要在基类或通用模板里添加
Vector和VectorView自动获得所有公共操作,不用重复实现- 运算符自动兼容两种类型的任意组合,不用手动写多个重载
内容来源于stack exchange
相关产品推荐
相关产品推荐

