顺德网站建设海淀网站建设

武汉迈极环保科技有限公司 2026/09/09 18:21:25

一、背景

C++ 代码似乎经常出现一个问题:如果该值可以来自左值或右值,则对象如何跟踪该值?即如果保留该值作为引用,那么就无法绑定到临时对象。如果将其保留为一个值,那么当它从左值初始化时,会产生不必要的副本。

有几种方法可以应对这种情况。使用std::variant提供了一个很好的折衷方案来获得有表现力的代码。

二、跟踪值

假设有一个类MyClass。想让MyClass访问某个std::string。如何表示MyClass内部的字符串?有两种选择:

  • 将其存储为引用。

  • 将其存储为副本。

2.1、存储引用

如果将其存储为引用,例如const引用:

展开

代码语言:C++

自动换行

AI代码解释

class MyClass { public: explicit MyClass(std::string const& s) : s_(s) {} void print() const { std::cout << s_ << ' '; } private: std::string const& s_; };

则可以用一个左值初始化我们的引用:

代码语言:C++

自动换行

AI代码解释

std::string s = "hello"; MyClass myObject{s}; myObject.print();

看起来很不错。但是,如果想用右值初始化我们的对象呢?例如:

代码语言:C++

自动换行

AI代码解释

MyClass myObject{std::string{"hello"}}; myObject.print();

或者这样的代码:

代码语言:C++

自动换行

AI代码解释

std::string getString(); // function declaration returning by value MyClass myObject{getString()}; myObject.print();

那么代码具有未定义的行为。原因是,临时字符串对象在创建它的同一条语句中被销毁。当调用print时,字符串已经被破坏,使用它是非法的,并导致未定义的行为。

为了说明这一点,如果将std::string替换为类型X,并且在X的析构函数打印日志:

展开

代码语言:C++

自动换行

AI代码解释

struct X { ~X() { std::cout << "X destroyed" << ' ';} }; class MyClass { public: explicit MyClass(X const& x) : x_(x) {} void print() const { // using x_; } private: X const& x_; };

在调用的地方也打印日志:

代码语言:C++

自动换行

AI代码解释

MyClass myObject(X{}); std::cout << "before print" << ' '; myObject.print();

输出:

代码语言:Bash

自动换行

AI代码解释

X destroyed before print

可以看到,在尝试使用之前,这个X已经被破坏了。

完整示例:

展开

代码语言:C++

自动换行

AI代码解释

#include <iostream> #include <string> struct X { ~X() { std::cout << "X destroyed" << ' ';} }; class MyClass { public: explicit MyClass(X const& x) : x_(x) {} void print() { (void) x_; // using x_; } private: X const& x_; }; int main() { MyClass myObject(X{}); std::cout << "before print" << ' '; myObject.print(); }

2.2、存储值

另一种选择是存储一个值。这允许使用move语义将传入的临时值移动到存储值中:

展开

代码语言:C++

自动换行

AI代码解释

class MyClass { public: explicit MyClass(std::string s) : s_(std::move(s)) {} void print() const { std::cout << s_ << ' '; } private: std::string s_; };

现在调用它:

代码语言:C++

自动换行

AI代码解释

MyClass myObject{std::string{"hello"}}; myObject.print();

产生两次移动(一次构造s,一次构造s_),并且没有未定义的行为。实际上,即使临时对象被销毁,print也会使用类内部的实例。

不幸的是,如果带着左值返回到第一个调用点:

代码语言:C++

自动换行

AI代码解释

std::string s = "hello"; MyClass myObject{s}; myObject.print();

那么就不再做两次移动了:做了一次复制(构造s)和一次移动(构造s_)。

更重要的是,我们的目的是给MyClass访问字符串的权限,如果做一个拷贝,就有了一个不同于进来的实例。所以它们不会同步。

对于临时对象来说,这不是问题,因为它无论如何都会被销毁,并且我们在之前将它移了进来,所以仍然可以访问字符串。但是通过复制,我们不再给MyClass访问传入字符串的权限。

所以存储一个值也不是一个好的解决方案。

三、存储variant

存储引用不是一个好的解决方案,存储值也不是一个好的解决方案。我们想做的是,如果引用是从左值初始化的,则存储引用;如果引用是从右值初始化的,则存储引用。

但是数据成员只能是一种类型:值或引用,对吗?

但是,对于std::variant,它可以是任意一个。不过,如果尝试在一个变量中存储引用,就像这样:

代码语言:C++

自动换行

AI代码解释

std::variant<std::string, std::string const&>

将得到一个编译错误:

代码语言:C++

自动换行

AI代码解释

variant must have no reference alternative

为了达到我们的目的,需要将引用放在另一个类型中;即必须编写特定的代码来处理数据成员。如果为std::string编写这样的代码,则不能将其用于其他类型。

在这一点上,最好以通用的方式编写代码。

四、通用存储类

存储需要是一个值或一个引用。既然现在是为通用目的编写这段代码,那么也可以允许非const引用。由于变量不能直接保存引用,那么可以将它们存储到包装器中:

展开

代码语言:C++

自动换行

AI代码解释

template<typename T> struct NonConstReference { T& value_; explicit NonConstReference(T& value) : value_(value){}; }; template<typename T> struct ConstReference { T const& value_; explicit ConstReference(T const& value) : value_(value){}; }; template<typename T> struct Value { T value_; explicit Value(T&& value) : value_(std::move(value)) {} };

将存储定义为这两种情况之一:

代码语言:C++

自动换行

AI代码解释

template<typename T> using Storage = std::variant<Value<T>, ConstReference<T>, NonConstReference<T>>;

现在需要通过提供引用来访问变量的底层值。创建了两种类型的访问:一种是const,另一种是非const

4.1、定义const访问

要定义const访问,需要使变量内部的三种可能类型中的每一种都产生一个const引用。

为了访问变量中的数据,将使用std::visit和规范的overload模式,这可以在c++ 17中实现:

代码语言:C++

自动换行

AI代码解释

template<typename... Functions> struct overload : Functions... { using Functions::operator()...; overload(Functions... functions) : Functions(functions)... {} };

要获得const引用,只需为每种variant创建一个:

展开

代码语言:C++

自动换行

AI代码解释

template<typename T> T const& getConstReference(Storage<T> const& storage) { return std::visit( overload( [](Value<T> const& value) -> T const& { return value.value_; }, [](NonConstReference<T> const& value) -> T const& { return value.value_; }, [](ConstReference<T> const& value) -> T const& { return value.value_; } ), storage ); }

4.2、定义非const访问

非const引用的创建使用相同的技术,除了variantConstReference之外,它不能产生非const引用。然而,当std::visit访问一个变量时,必须为它的每一个可能的类型编写代码:

展开

代码语言:C++

自动换行

AI代码解释

template<typename T> T& getReference(Storage<T>& storage) { return std::visit( overload( [](Value<T>& value) -> T& { return value.value_; }, [](NonConstReference<T>& value) -> T& { return value.value_; }, [](ConstReference<T>& ) -> T&. { /* code handling the error! */ } ), storage ); }

进一步优化,抛出一个异常:

展开

代码语言:C++

自动换行

AI代码解释

struct NonConstReferenceFromReference : public std::runtime_error { explicit NonConstReferenceFromReference(std::string const& what) : std::runtime_error{what} {} }; template<typename T> T& getReference(Storage<T>& storage) { return std::visit( overload( [](Value<T>& value) -> T& { return value.value_; }, [](NonConstReference<T>& value) -> T& { return value.value_; }, [](ConstReference<T>& ) -> T& { throw NonConstReferenceFromReference{"Cannot get a non const reference from a const reference"} ; } ), storage ); }

五、创建存储

已经定义了存储类,可以在示例中使用它来访问传入的std::string,而不管它的值类别:

展开

代码语言:C++

自动换行

AI代码解释

class MyClass { public: explicit MyClass(std::string& value) : storage_(NonConstReference(value)){} explicit MyClass(std::string const& value) : storage_(ConstReference(value)){} explicit MyClass(std::string&& value) : storage_(Value(std::move(value))){} void print() const { std::cout << getConstReference(storage_) << ' '; } private: Storage<std::string> storage_; };

(1)调用时带左值:

代码语言:C++

自动换行

AI代码解释

std::string s = "hello"; MyClass myObject{s}; myObject.print();

匹配第一个构造函数,并在存储成员内部创建一个NonConstReference。当print函数调用getConstReference时,非const引用被转换为const引用。

(2)使用临时值:

代码语言:C++

自动换行

AI代码解释

MyClass myObject{std::string{"hello"}}; myObject.print();

这个函数匹配第三个构造函数,并将值移动到存储中。getConstReference然后将该值的const引用返回给print函数。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

专业网站建设建设企业网站的

OpCore Simplify终极指南:一键搞定专业级黑苹果EFI配置【免费下载链接】OpCore-SimplifyA tool designed to simplify the cre

2026/06/30 11:24:25

建设厅网站首页定制网站建设

Langchain-Chatchat边缘计算应用:物联网设备端智能问答尝试在工业现场,一名技术人员正面对一台故障设备。他掏出平板,语音提问:“如

2026/06/30 12:17:00

昆山网站建设徐州网站建设

还在为复杂的网络配置而头疼?S-UI Windows版为你带来革命性的一键安装体验!这个强大的网络管理面板工具专为Windows平台设计,让你轻松管理各种网络

2026/06/30 12:55:33

常州网站建设聊城网站建设

同花顺问财数据获取:Python自动化工具的完整使用指南【免费下载链接】pywencai获取同花顺问财数据项目地址: https://gitcode.com/gh_mirrors/py/

2026/06/30 10:57:22

企业网站建设方案北京网站建设公司

目录已开发项目效果实现截图开发技术介绍系统开发工具:核心代码参考示例1.建立用户稀疏矩阵,用于用户相似度计算【相似度矩阵】2.计算目标用户与其他用户的相似度系统测试总结源码

2026/06/30 12:43:02

网站建设维护黑龙江网站建设

目录系统概述技术架构功能模块应用价值项目技术支持论文大纲核心代码部分展示可定制开发之亮点部门介绍结论源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作系统概述Sp

2026/06/30 12:12:30

广州网站建设工作室建设官方网站

百度网盘下载速度革命:解锁全速下载的实战手册【免费下载链接】baidu-wangpan-parse获取百度网盘分享文件的下载地址项目地址: https://gitcode.com/gh_

2026/06/30 11:17:54

房地产网站建设天津网站建设公司

Markdown引用块使用:突出TensorFlow权威资料来源在AI工程实践中,一个常见的痛点是“环境不一致”——开发者常说的“在我机器上能跑”,到了同事或

2026/06/30 12:51:33

西安网站建设网站建设规划

文章目录基于Springboot开发的精简博客系统的设计与实现一、项目简介(源代码在文末)1.运行视频2.🚀 项目技术栈3.✅ 环境要求说明4.包含的文件列

2026/06/30 12:44:02