You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何兼顾类型替换能力与里氏替换原则?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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 13:50:16