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

成都成峰叉车机械设备有限公司 2026/09/09 19:06:45

一、背景

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函数。

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

梧州网站建设网站首页建设

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

2026/06/30 11:31:56

网站建设一条龙鞍山网站建设

终极指南:如何快速安装Transformer Explainer可视化学习工具【免费下载链接】transformer-explainerTransformer Explained Vis

2026/06/30 13:43:37

陕西网站建设江苏省建设厅网站

前言随着教育数字化转型的推进,传统校园管理模式存在流程繁琐、信息传递滞后、服务触达不便等问题,难以满足师生多元化的校园服务需求。本课题聚焦校园管理的核心痛点,

2026/06/30 13:05:04

宁波网站建设桂林网站建设

第一章:MCP Azure虚拟机迁移概述将本地虚拟化工作负载迁移到 Microsoft Azure 是现代企业实现云转型的重要步骤。MCP(Microsoft Cloud

2026/06/30 12:08:59

凯里网站建设内蒙古网站建设

第一章:Open-AutoGLM手机AI助手的演进全景Open-AutoGLM作为新一代开源手机AI助手框架,融合了大语言模型与移动端轻量化推理技术,推动智能

2026/06/30 14:08:08

九江网站建设濮阳网站建设

脚本加密技术全解析1. 加密概述加密是混淆脚本极为有效的方法,使用包装器(wrapper)对脚本进行加密,不仅能增加逆向工程的难度,而且若操作得当,没有正确密钥,任何人都无法运行脚本。不过,这种技术也

2026/06/30 14:15:09

网站建设服务胶州网站建设

iOS个性化革命:Cowabunga Lite全方位定制指南【免费下载链接】CowabungaLiteiOS 15+ Customization Toolbox项目地址: htt

2026/06/30 13:15:35

网站建设流程富阳网站建设

PojavLauncher iOS完整指南:在iPhone上运行Minecraft Java版的终极教程【免费下载链接】PojavLauncher_iOSA Minecraft: Jav

2026/06/30 10:29:51

静安网站建设六安网站建设

Vitis AI推理延迟优化实战:从模型到硬件的全链路加速在边缘计算和实时AI系统中,“跑得快”往往比“跑得通”更重要。当你把一个训练好的PyTorch模型部署到ZCU10

2026/06/30 13:02:04