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

能否实现仅存储左值引用或对象的C++模板类?

实现兼具左值引用存储与右值对象存储的C++模板类

你要的这个类是一个"可区分引用/值"的包装器,核心要解决两个痛点:消除std::optional的无意义构造开销,优化get()的冗余分支,同时让函数能统一接收左值、右值构造的包装器。以下是两种可行的实现方案:


方案一:用Union手动管理存储(极致高效)

这种方式直接用union存储指针或对象,完全避免不必要的构造开销,手动控制析构逻辑:

#include <utility> // std::move

template<typename T>
class ref {
private:
    union Storage {
        T* ptr;
        T obj;
        Storage() {} // 不初始化任何成员,避免默认构造开销
        ~Storage() {} // 不自动析构,由外部控制
    } m_storage;
    bool m_is_lvalue;

    // 辅助函数:按需析构对象
    void destroy_if_object() noexcept {
        if (!m_is_lvalue) {
            m_storage.obj.~T();
        }
    }

public:
    // 左值构造:存储指针
    ref(T& lvalue) noexcept : m_is_lvalue(true) {
        new (&m_storage.ptr) T*(&lvalue);
    }

    // 右值构造:存储移动构造的对象
    ref(T&& rvalue) noexcept(std::is_nothrow_move_constructible_v<T>) 
        : m_is_lvalue(false) {
        new (&m_storage.obj) T(std::move(rvalue));
    }

    // 析构函数:按需清理资源
    ~ref() noexcept {
        destroy_if_object();
    }

    // 禁用拷贝(避免指针悬空或对象拷贝)
    ref(const ref&) = delete;
    ref& operator=(const ref&) = delete;

    // 移动构造:转移资源,无拷贝
    ref(ref&& other) noexcept(std::is_nothrow_move_constructible_v<T>) 
        : m_is_lvalue(other.m_is_lvalue) {
        if (m_is_lvalue) {
            new (&m_storage.ptr) T*(other.m_storage.ptr);
        } else {
            new (&m_storage.obj) T(std::move(other.m_storage.obj));
            // 标记原对象为左值,避免其析构时销毁已转移的对象
            other.m_is_lvalue = true;
        }
    }

    // 移动赋值
    ref& operator=(ref&& other) noexcept(std::is_nothrow_move_constructible_v<T>) {
        if (this == &other) return *this;

        // 先清理自身资源
        destroy_if_object();

        m_is_lvalue = other.m_is_lvalue;
        if (m_is_lvalue) {
            m_storage.ptr = other.m_storage.ptr;
        } else {
            new (&m_storage.obj) T(std::move(other.m_storage.obj));
            other.m_is_lvalue = true;
        }
        return *this;
    }

    // 获取引用
    T& get() noexcept {
        return m_is_lvalue ? *m_storage.ptr : m_storage.obj;
    }

    const T& get() const noexcept {
        return m_is_lvalue ? *m_storage.ptr : m_storage.obj;
    }

    // 重载运算符,简化使用
    T* operator->() noexcept { return &get(); }
    const T* operator->() const noexcept { return &get(); }
    T& operator*() noexcept { return get(); }
    const T& operator*() const noexcept { return get(); }
};

// 推导指南,允许自动推导模板参数
template<typename T>
ref(T&) -> ref<T>;

template<typename T>
ref(T&&) -> ref<T>;

方案优势:

  • 零冗余存储:union仅占用max(sizeof(T*), sizeof(T))的空间,无额外开销
  • 无不必要构造:存储左值时完全不构造T对象,彻底解决原实现中std::optional的默认构造问题
  • 移动语义高效:仅转移资源,无拷贝操作

方案二:用std::variant实现(安全简洁)

如果不想手动处理union的构造析构,可以用C++17的std::variant,它会自动管理成员的生命周期,代码更简洁安全:

#include <variant> // std::variant
#include <functional> // std::reference_wrapper
#include <utility> // std::move

template<typename T>
class ref {
private:
    std::variant<std::reference_wrapper<T>, T> m_data;

public:
    // 左值构造:存储引用包装器
    ref(T& lvalue) noexcept : m_data(std::ref(lvalue)) {}

    // 右值构造:存储移动构造的对象
    ref(T&& rvalue) noexcept(std::is_nothrow_move_constructible_v<T>) 
        : m_data(std::move(rvalue)) {}

    // 禁用拷贝,保证无拷贝操作
    ref(const ref&) = delete;
    ref& operator=(const ref&) = delete;

    // 移动构造和赋值默认即可,std::variant会自动处理
    ref(ref&&) noexcept = default;
    ref& operator=(ref&&) noexcept = default;

    // 获取引用:用std::visit配合编译时分支消除冗余检查
    T& get() noexcept {
        return std::visit([](auto& val) -> T& {
            if constexpr (std::is_same_v<decltype(val), std::reference_wrapper<T>&>) {
                return val.get();
            } else {
                return val;
            }
        }, m_data);
    }

    const T& get() const noexcept {
        return std::visit([](auto& val) -> const T& {
            if constexpr (std::is_same_v<decltype(val), const std::reference_wrapper<T>&>) {
                return val.get();
            } else {
                return val;
            }
        }, m_data);
    }

    // 重载运算符
    T* operator->() noexcept { return &get(); }
    const T* operator->() const noexcept { return &get(); }
    T& operator*() noexcept { return get(); }
    const T& operator*() const noexcept { return get(); }
};

// 推导指南
template<typename T>
ref(T&) -> ref<T>;

template<typename T>
ref(T&&) -> ref<T>;

方案优势:

  • 代码简洁安全:无需手动管理构造析构,std::variant自动处理生命周期
  • 编译时分支优化:if constexpr让编译器为两种情况生成专属代码,运行时开销极低

使用方式

无论是哪种方案,你都可以像这样使用:

struct bar { /* ... */ };

void foo(ref<bar> r) {
    // 三种访问方式等价
    r.get().some_method();
    (*r).some_method();
    r->some_method();
}

int main() {
    bar lvalue_bar;
    foo(lvalue_bar); // 自动构造ref<bar>存储左值引用

    foo(bar{}); // 自动构造ref<bar>存储右值对象
}

关于运行时检查的说明

因为你需要ref<T>作为统一的类型,同时兼容左值和右值构造的情况,运行时的类型判断是不可避免的。但上述两种方案都将开销降到了最低:要么是一个bool判断,要么是std::variant的索引判断,编译器通常会将其优化为极高效的分支。

内容的提问来源于stack exchange,提问作者TwistedBlizzard

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 20:44:52