如何兼顾类型替换能力与里氏替换原则?C++类设计求解
问题与解决方案:带所有权语义的受限Span替代类
问题背景
我们有一个模板类A(可类比标准库的span),以及一个接收A的函数one_two_three。需要设计一个目标类满足:
- 内部包含
A的T*成员逻辑 - 能够直接传给接受
A的函数 - 比
A更受限(禁止外部直接访问内部的指针成员) - 附加类似
unique_ptr的独占所有权语义(析构时自动释放资源)
现有两个尝试的问题:
- 类
B:完全封装了A,但无法无缝替换A传入one_two_three(存在模板推导或转发场景的限制) - 类
C:通过继承A实现了可替换性,但违反里氏替换原则(LSP)——继承暴露了A的公共成员,无法实现“更受限”的要求,也不符合所有权语义的设计初衷
需要一个兼顾两者优势的实现,优先C++11方案,也可接受新标准。
现有代码示例
template<typename T> struct A { T* ptr; }; template <typename T> int one_two_three(A<T> a) { return 123; } template<typename T> class B { public: using a_type = A<T>; B(T* ptr) : a_{ptr} { } a_type get() const noexcept { return a_; } operator a_type() const noexcept { return get(); } private: a_type a_; }; template<typename T> class C : public A<T> { public: using a_type = A<T>; C(T* ptr) : a_type{ptr} { } a_type get() const noexcept { return *this; } operator a_type() const noexcept { return get(); } };
C++11 实现方案
核心思路:通过**隐式转换到A<T>**实现可替换性,同时完全封装内部的A<T>实例来保证受限性,再添加移动语义和析构逻辑实现所有权管理。
#include <utility> template<typename T> struct A { T* ptr; }; template <typename T> int one_two_three(A<T> a) { return 123; } template<typename T> class OwnedSpan { public: using span_type = A<T>; // 接管指针所有权(explicit避免意外转换) explicit OwnedSpan(T* ptr) : span_{ptr} {} // 移动构造:转移资源所有权 OwnedSpan(OwnedSpan&& other) noexcept : span_{other.span_.ptr} { other.span_.ptr = nullptr; } // 移动赋值:转移资源所有权并释放当前资源 OwnedSpan& operator=(OwnedSpan&& other) noexcept { if (this != &other) { delete[] span_.ptr; // 根据资源类型调整为delete/delete[] span_.ptr = other.span_.ptr; other.span_.ptr = nullptr; } return *this; } // 禁用拷贝:保证独占所有权 OwnedSpan(const OwnedSpan&) = delete; OwnedSpan& operator=(const OwnedSpan&) = delete; // 析构:自动释放资源 ~OwnedSpan() noexcept { delete[] span_.ptr; } // 隐式转换为A<T>,支持直接传入接受A<T>的函数 operator span_type() const noexcept { return span_; } // 显式获取span的方法(可选) span_type get_span() const noexcept { return span_; } private: span_type span_; // 完全封装,外部无法访问内部成员 }; // 使用示例 int main() { int* arr = new int[5]; OwnedSpan<int> os(arr); // 自动隐式转换为A<int>,直接传入函数 int result = one_two_three(os); return result; }
方案特点
- 受限性:内部
span_type为私有成员,外部无法直接访问ptr - 可替换性:隐式转换运算符让
OwnedSpan<T>能无缝适配所有接受A<T>的函数 - 所有权语义:独占资源,移动语义避免资源重复释放,析构自动清理
- C++11兼容:所有特性均为C++11支持
C++17+ 优化方案
如果可以使用C++17及以上标准,直接利用标准库的std::span和std::unique_ptr简化实现:
#include <span> #include <memory> template <typename T> int one_two_three(std::span<T> s) { return 123; } template<typename T> class OwnedSpan { public: using span_type = std::span<T>; // 从unique_ptr接管数组所有权 explicit OwnedSpan(std::unique_ptr<T[]> ptr, std::size_t size) : ptr_{std::move(ptr)}, span_{ptr_.get(), size} {} // 直接分配指定大小的数组(简化接口) explicit OwnedSpan(std::size_t size) : ptr_{std::make_unique<T[]>(size)}, span_{ptr_.get(), size} {} // 默认移动构造/赋值 OwnedSpan(OwnedSpan&&) noexcept = default; OwnedSpan& operator=(OwnedSpan&&) noexcept = default; // 禁用拷贝 OwnedSpan(const OwnedSpan&) = delete; OwnedSpan& operator=(const OwnedSpan&) = delete; // 隐式转换为std::span<T> operator span_type() const noexcept { return span_; } // 显式获取span span_type get_span() const noexcept { return span_; } private: std::unique_ptr<T[]> ptr_; // 管理资源所有权 span_type span_; // 提供span视图 }; // 使用示例 int main() { OwnedSpan<int> os(5); int result = one_two_three(os); return result; }
优化亮点
- 复用标准库组件,减少自定义代码的维护成本
std::span提供更完善的视图功能,兼容性更强- C++17默认移动语义简化了代码实现
内容的提问来源于stack exchange,提问作者einpoklum
相关产品推荐
相关产品推荐

