首页 > 编程语言 >[Effective Modern C++] 条款18笔记

[Effective Modern C++] 条款18笔记

时间:2024-06-21 17:14:33浏览次数:12  
标签:std Effective 18 Modern pInv params forward unique ptr

条款18中的完整代码:

点击查看代码
#include <iostream>
#include <memory>
#include <string>

// 假设基础的 Investment 类和 Stock 类
class Investment {
public:
    virtual ~Investment() = default;
    virtual void display() const = 0;
};
// 其它类类似,略
class Stock : public Investment {
    std::string name;
public:
    Stock(const std::string& stockName) : name(stockName) {}
    void display() const override {
        std::cout << "Stock: " << name << std::endl;
    }
};

void makeLogEntry(Investment* pInvestment) {
    std::cout << "Logging investment action" << std::endl;
}

template<typename... Ts>
auto makeInvestment(Ts&&... params) 
{
    // 自定义删除器
    auto delInvmt = [](Investment* pInvestment) 
    {
        makeLogEntry(pInvestment);
        delete pInvestment;
    };

    // 使用 std::unique_ptr 来管理 Investment 对象,指定 delInvmt 作为自定义删除器
    std::unique_ptr<Investment, decltype(delInvmt)> pInv(nullptr, delInvmt);

    // 根据条件创建 Investment 对象
	if ( /* a Stock object should be created */ )
	{
		pInv.reset(new Stock(std::forward<Ts>(params)...));
	}
	else if ( /* a Bond object should be created */ )
	{
		pInv.reset(new Bond(std::forward<Ts>(params)...));
	}
	else if ( /* a RealEstate object should be created */ )
	{
		pInv.reset(new RealEstate(std::forward<Ts>(params)...));
	}

    return pInv; // 返回 unique_ptr,发生移动构造
}

int main() {
    auto investment = makeInvestment("TechCorp");
    if (investment) {
        investment->display();
    }
    return 0;
}

针对上述代码有两点疑问:

首先,为什么params需要使用std::forward,而不能直接赋值;其次,return pInv;返回unique_ptr是否发生了复制构造操作?

这两个问题涉及到 C++11/14 中的完美转发(perfect forwarding)和智能指针的行为。我会详细解释为什么在 makeInvestment 中使用 std::forward,以及 std::unique_ptr 的返回过程是否会发生复制构造操作。

1. 为什么 params 需要使用 std::forward,而不能直接赋值?

在 C++ 中,函数模板和可变参数模板允许我们编写接受任意类型和数量参数的函数。为了正确处理这些参数的值类别(lvalue/rvalue),我们需要使用 std::forward

完美转发(Perfect Forwarding)

  • 完美转发 是指在函数模板中保留参数的原始值类别(是左值还是右值),以便在调用其他函数时保持参数的最佳传递方式。
  • 使用 std::forward 可以将参数以其原始的值类别传递给另一个函数。例如,如果一个参数在调用时是右值(临时对象),我们希望它继续作为右值传递下去,以便利用移动语义来提高性能;而如果它是左值,我们希望它继续作为左值传递。

示例

假设 makeInvestment 函数模板传递了一个右值参数,而我们希望在创建 Stock 对象时,该右值参数能利用移动构造函数进行高效的对象创建。如果我们不使用 std::forward,将直接使用参数构造 Stock 对象,可能会导致多余的拷贝操作,无法充分利用 C++11 提供的移动语义

具体原因:

template<typename... Ts>
auto makeInvestment(Ts&&... params) 
{
    if ( /* 条件 */ ) 
    {
        // 创建 Stock 对象时,使用 std::forward 保持参数的原始值类别
        pInv.reset(new Stock(std::forward<Ts>(params)...));
    }

    return pInv;
}
  • 没有 std::forward 的情况:

    • 如果我们直接使用 params... 传递给 Stock 的构造函数:
      pInv.reset(new Stock(params...));
      
    • 这种情况下,所有的参数都会被视为左值(即使它们最初是右值)。这可能导致不必要的拷贝构造,而不是移动构造,特别是在处理右值时。
  • std::forward 的情况:

    • std::forward<Ts>(params)... 能确保每个参数都以其原始的值类别传递(右值保持右值,左值保持左值)。
    • 对于右值参数,这意味着它们会利用移动构造或移动赋值运算符,这通常是更高效的。

总结来说,std::forward 确保了参数在转发时能够保持它们的值类别,从而避免不必要的拷贝,提高了效率和性能,特别是在处理临时对象和大数据结构时。

2. return pInv 是否会发生 unique_ptr 的复制构造操作?

std::unique_ptr 是一个独占所有权的智能指针,它不支持复制构造,但支持移动构造。这意味着 std::unique_ptr 不能被复制,但可以被移动。

返回 std::unique_ptr 时的行为:

return pInv;
  • 移动语义(Move Semantics):
    • std::unique_ptr 被返回时,编译器会自动使用移动构造函数来将所有权转移到返回的 std::unique_ptr 中。
    • 由于 std::unique_ptr 不支持复制构造,所以编译器不会尝试进行复制,而是使用移动构造来转移资源。
    • 这符合 C++11/14 的移动语义设计,使得返回 std::unique_ptr 高效且安全。

详细解释:

当我们在函数中返回一个 std::unique_ptr 时,如 return pInv;

  • 没有移动构造的显式调用

    • 编译器会尝试自动优化,通常是通过移动构造将返回的对象转移到调用者。
    • 这意味着函数返回时,pInv 所指向的资源的所有权会被转移到接收返回值的对象中,而 pInv 自身将不再持有该资源。
  • 返回值优化(RVO, Return Value Optimization)

    • 在很多情况下,编译器会进行返回值优化,直接构造返回的 unique_ptr 对象,而不是先在函数内部创建,然后再移动到外部。
    • 这种优化消除了不必要的对象构造和析构,提高了性能。

所以,在返回 std::unique_ptr 时,不会进行复制构造,而是使用移动构造或通过返回值优化直接生成最终的对象,保证了效率和资源管理的安全性。

在这个示例中:

  • std::forward 保证了参数 params 被以正确的值类别传递给 Stock 的构造函数。
  • return pInv; 返回时使用了 std::unique_ptr 的移动语义,确保资源的高效管理和转移。

标签:std,Effective,18,Modern,pInv,params,forward,unique,ptr
From: https://www.cnblogs.com/nuo-chen/p/18260913

相关文章

  • [Effective Modern C++] 条款19笔记 - 为什么deleter的类型是std::unique_ptr类型的一
    为什么deleter的类型是std::unique_ptr类型的一部分,而不是std::shared_ptr的一部分?std::unique_ptr<Widget,decltype(loggingDel)>upw(newWidget,loggingDel);std::shared_ptr<Widget>upw(newWidget,loggingDel);这个问题涉及到std::unique_ptr和std::shared_ptr......
  • MYSQL基础_18_MySQL8其它新特性
    第18章_MySQL8其它新特性1.MySQL8新特性概述MySQL从5.7版本直接跳跃发布了8.0版本,可见这是一个令人兴奋的里程碑版本。MySQL8版本在功能上做了显著的改进与增强,开发者对MySQL的源代码进行了重构,最突出的一点是多MySQLOptimizer优化器进行了改进。不仅在速度上得到了改......
  • SSL/TLS协议信息泄露漏洞(CVE-2016-2183)
    1.问题描述SSL/TLS协议信息泄露漏洞(CVE-2016-2183)TLS是安全传输层协议,用于在两个通信应用程序之间提供保密性和数据完整性。TLS,SSH,IPSec协商及其他产品中使用的DES及TripleDES密码存在大约四十亿块的生日界,这可使远程攻击者通过Sweet32攻击,获取纯文本数据。2.问题解决......
  • “数字东哥”出圈,618风向转变
    618已经走过了十几年。随着技术、用户需求以及商业形态的变化,618也进化到了一个新阶段。今年618,在一片低价促销声中,京东的数字人直播格外抢眼。4月中旬,由刘强东数字人直播点燃了第一把火,在收官阶段,再由18位企业创始人的数字分身在京东接力直播。这也让我们看到电商平台发展的新风......
  • 题解:CF1829H Don't Blame Me
    动态规划好题。对于此题解,不懂的问题可以私信笔者。前置知识解题方法用\(dp_{i,j}\)表示前\(i\)个数选择了若干个数按位与之后为\(j\)的子序列个数。接下来思考转移。想到这里,你会发现按位与没有逆运算,一次我们要正推,例如\(f_{i+2}=f_{i}+f_{i+1}\)。那么转移方程不......
  • 【Effective Python教程】(90个有效方法)笔记——第3章:函数——23:用关键字参数来传参(位
    文章目录第3章:函数第23条用关键字参数来传参位置传递参数关键字传递参数位置和关键字传递参数混合使用另外,关键字形式与位置形式也可以混用。下面这四种写法的效果相同:==如果混用,那么位置参数必须出现在关键字参数之前,否则就会出错。==每个参数只能指定一次,不能既通过位......
  • Effective Java 学习总结
    前言EffectiveJava作为Java四大名著之一,聚焦于Java语言习惯和高效的用法。EJ告诉读者如何更好地构建代码,以便代码能够更好地工作;也便于其他人能够理解这些代码,便于修改和改善;程序也会因此变得更加令人愉快,更加优雅。全书共90条,接下来笔者将逐条进行总结。第1条:用......
  • 18
    资源优化资源优化是性能优化的基础,包括纹理优化、UI优化和字体优化。例如,可以通过减小纹理尺寸、减少纹理通道、提高纹理复用率和使用合适的压缩格式来减少内存占用。 渲染优化渲染优化主要涉及到减少DrawCalls和优化着色器。可以通过使用Batching技术将多个小的纹理合并到......
  • 团队冲刺18
    资源优化资源优化是性能优化的基础,包括纹理优化、UI优化和字体优化。例如,可以通过减小纹理尺寸、减少纹理通道、提高纹理复用率和使用合适的压缩格式来减少内存占用。渲染优化渲染优化主要涉及到减少DrawCalls和优化着色器。可以通过使用Batching技术将多个小的纹理合并到一个......
  • react 18 基础教程
    1.React开发环境搭建执行npxcreate-react-app项目名称命令来创建项目2.实现列表渲染在react中可以通过在{}中写入js表达式来执行js代码,所以可以通过如下手段来执行来实现列表的渲染。functionApp(){letlist=[{id:1,name:"Vue"},{id:2,name:"React"},......