当前位置: 首页 > news >正文

More Effective C++ 条款19:理解临时对象的来源(Understand the Origin of Temporary Objects)

More Effective C++ 条款19:理解临时对象的来源(Understand the Origin of Temporary Objects)


核心思想临时对象是由编译器为了满足函数调用、类型转换或表达式求值而创建的无名对象。它们可能会带来性能开销,因此理解其产生条件有助于避免不必要的临时对象,提升程序效率。

🚀 1. 问题本质分析

1.1 临时对象的定义

  • 临时对象:编译器为满足语法或语义要求而自动创建的未命名对象
  • 生命周期:通常存在于创建它们的完整表达式结束时
  • 常见场景:函数返回值、类型转换、表达式求值

1.2 临时对象与局部对象的区别

// ❌ 临时对象(编译器自动创建)
std::string getString() {return "Hello";  // 创建临时std::string对象
}void processString(const std::string& str);processString("Hello");  // 创建临时std::string对象// ✅ 局部对象(程序员显式创建)
void example() {std::string localStr = "Hello";  // 局部对象processString(localStr);         // 无临时对象
}

📦 2. 问题深度解析

2.1 函数返回值的临时对象

// 场景1:返回非引用类型
std::string createString() {return std::string("Temporary");  // 创建临时对象
}void useString() {std::string s = createString();  // 临时对象用于初始化sconst std::string& ref = createString();  // 临时对象绑定到引用
}// 场景2:返回大型对象时的优化
class LargeObject {
public:LargeObject() { /* 昂贵构造 */ }LargeObject(const LargeObject&) { /* 昂贵拷贝 */ }LargeObject(LargeObject&&) { /* 移动语义 */ }
};LargeObject createLargeObject() {return LargeObject();  // 可能使用RVO/NRVO优化
}void useLargeObject() {LargeObject obj = createLargeObject();  // 希望避免额外拷贝
}

2.2 类型转换产生的临时对象

// 场景1:参数类型不匹配
void printString(const std::string& str);printString("Hello");  // 从const char*到std::string的转换创建临时对象// 场景2:运算符重载
class Complex {
public:Complex(double real, double imag = 0.0);Complex operator+(const Complex& other) const;
};Complex c1(1.0, 2.0);
Complex c2 = c1 + 3.0;  // 3.0转换为Complex(3.0)临时对象// 场景3:显式类型转换
double d = 3.14;
int i = static_cast<int>(d);  // 创建临时int对象(但通常优化掉)

2.3 表达式求值中的临时对象

// 场景1:运算符重载链
Complex c1(1.0), c2(2.0), c3(3.0);
Complex result = c1 + c2 + c3;  // 中间结果产生临时对象// 场景2:函数调用链
std::string getPrefix();
std::string getSuffix();std::string result = getPrefix() + getSuffix();  // 中间临时对象// 场景3:条件运算符
int a = 5, b = 10;
int max = (a > b) ? a : b;  // 不产生临时对象(相同类型)
double ratio = (a > b) ? a : 1.5;  // 可能产生临时对象(不同类型)

⚖️ 3. 解决方案与最佳实践

3.1 避免不必要的临时对象

// ✅ 使用引用避免拷贝
void processLargeObject(const LargeObject& obj);  // 传递引用而非值// ✅ 使用移动语义
LargeObject createObject() {LargeObject obj;// 设置objreturn obj;  // 可能使用移动语义或RVO
}// ✅ 使用emplace_back避免临时对象
std::vector<std::string> strings;
strings.push_back("Hello");       // 创建临时std::string然后拷贝/移动
strings.emplace_back("Hello");    // 直接在vector中构造,无临时对象// ✅ 使用string_view避免字符串临时对象
void processString(std::string_view str);  // 接受任何字符串类型,无转换processString("Hello");           // 无临时std::string对象
processString(std::string("Hi")); // 无额外临时对象

3.2 利用现代C++特性减少临时对象

// ✅ 使用右值引用和移动语义
class ResourceHolder {
public:ResourceHolder(ResourceHolder&& other) noexcept : resource_(std::move(other.resource_)) {}ResourceHolder& operator=(ResourceHolder&& other) noexcept {resource_ = std::move(other.resource_);return *this;}private:std::unique_ptr<Resource> resource_;
};// ✅ 使用返回值优化(RVO/NRVO)
LargeObject createObject() {return LargeObject();  // RVO: 直接在调用处构造
}LargeObject createObject(bool flag) {LargeObject obj1, obj2;if (flag) {return obj1;  // NRVO: 具名返回值优化} else {return obj2;  // NRVO: 具名返回值优化}
}// ✅ 使用constexpr减少运行时临时对象
constexpr int computeValue(int x) {return x * x;  // 编译期计算,无运行时临时对象
}int value = computeValue(10);  // 编译期计算,无临时对象

3.3 优化表达式以减少临时对象

// ❌ 产生多个临时对象的表达式
std::string a = "Hello", b = " ", c = "World";
std::string result = a + b + c;  // 产生a+b的临时对象,再与c相加// ✅ 优化表达式减少临时对象
std::string result;
result.reserve(a.size() + b.size() + c.size());  // 预分配内存
result += a;
result += b;
result += c;  // 无额外临时对象// ✅ 使用ostringstream构建复杂字符串
std::ostringstream oss;
oss << a << b << c;  // 内部缓冲,减少临时对象
std::string result = oss.str();// ✅ 使用现代C++字符串连接
using namespace std::string_literals;
auto result = "Hello"s + " "s + "World"s;  // 但仍有临时对象// ✅ 最佳实践:使用append或operator+=
std::string result = a;
result.append(b).append(c);  // 链式调用,减少临时对象

3.4 类型系统优化

// ✅ 使用explicit构造函数避免隐式转换
class MyString {
public:explicit MyString(const char* str);  // 禁止隐式转换
};void processMyString(const MyString& str);// processMyString("Hello");  // 错误:需要显式转换
processMyString(MyString("Hello"));  // 正确:显式创建// ✅ 使用统一初始化语法
struct Point {int x, y;
};Point createPoint() {return {1, 2};  // 无临时对象,直接构造返回值
}// ✅ 使用auto和类型推导
auto str = std::string("Hello");  // 无额外临时对象
const auto& ref = createString(); // 延长临时对象生命周期// ✅ 使用完美转发
template<typename T>
void process(T&& arg) {// 完美转发,避免不必要的拷贝/移动internalProcess(std::forward<T>(arg));
}

💡 关键实践原则

  1. 识别临时对象产生场景
    关注以下可能产生临时对象的场景:

    void identifyTemporaries() {// 1. 参数类型不匹配void takesString(const std::string& s);takesString("hello");  // 临时std::string// 2. 函数返回非引用类型std::string getString();std::string s = getString();  // 可能涉及临时对象// 3. 表达式中的类型转换int a = 5;double b = 3.14;auto result = a + b;  // a转换为double临时对象// 4. 运算符重载链Matrix m1, m2, m3;auto result = m1 + m2 + m3;  // 中间临时矩阵对象
    }
    
  2. 使用工具检测临时对象
    通过性能分析工具和编译器输出检测临时对象:

    class Instrumented {
    public:Instrumented() { std::cout << "Constructor\n"; }Instrumented(const Instrumented&) { std::cout << "Copy Constructor\n"; }Instrumented(Instrumented&&) { std::cout << "Move Constructor\n"; }~Instrumented() { std::cout << "Destructor\n"; }
    };Instrumented createInstrumented() {return Instrumented();  // 观察构造/拷贝/移动次数
    }void testTemporaries() {Instrumented obj = createInstrumented();// 观察输出以了解临时对象行为
    }
    
  3. 应用优化策略
    根据具体情况选择合适的优化策略:

    // 策略1:使用引用传递
    void process(const BigObject& obj);  // 避免拷贝临时对象// 策略2:使用移动语义
    BigObject&& createBigObject();  // 返回右值引用// 策略3:使用emplace操作
    std::vector<BigObject> objects;
    objects.emplace_back(arg1, arg2);  // 直接构造,无临时对象// 策略4:使用string_view等非拥有视图
    void process(std::string_view str);  // 避免字符串转换// 策略5:优化表达式结构
    // 而不是: a + b + c + d
    // 使用: a.append(b).append(c).append(d)
    

现代C++中的临时对象优化工具

// 1. 返回值优化(RVO/NRVO)
BigObject create() {return BigObject();  // RVO
}BigObject create(bool flag) {BigObject obj1, obj2;return flag ? obj1 : obj2;  // NRVO
}// 2. 移动语义
BigObject createAndMove() {BigObject obj;return obj;  // 使用移动构造函数(如果NRVO不适用)
}// 3. 保证拷贝消除(C++17)
BigObject obj = BigObject(BigObject(BigObject()));  // C++17保证无拷贝// 4. constexpr计算
constexpr int compute(int x) { return x * x; }
int value = compute(10);  // 无运行时临时对象// 5. 结构化绑定
auto [x, y] = getPoint();  // 可能避免临时对象

代码审查要点

  1. 检查函数参数类型是否可能导致隐式转换产生临时对象
  2. 确认返回值优化是否可能(避免返回函数局部变量的引用)
  3. 检查是否可以使用emplace操作替代push_back/insert
  4. 确认是否可以使用string_view、span等非拥有视图类型
  5. 检查表达式结构是否可能产生不必要的中间临时对象
  6. 确认移动语义是否正确实现和使用
  7. 检查是否可以通过reserve预分配内存减少临时对象

总结
临时对象是编译器为满足语言规则而自动创建的无名对象,主要产生于函数返回值、类型转换和表达式求值等场景。虽然临时对象是语言机制的必要部分,但它们可能带来性能开销。理解临时对象的来源和生命周期是优化代码性能的关键。

通过使用引用传递、移动语义、返回值优化、emplace操作和非拥有视图类型等技术,可以显著减少不必要的临时对象。现代C++提供了多种工具和特性来帮助管理临时对象,包括RVO/NRVO、移动语义、constexpr计算和结构化绑定等。

在代码审查和性能优化时,应特别关注可能产生临时对象的场景,并应用适当的优化策略。然而,也需要注意不要过度优化,只有在性能关键路径上才需要重点关注临时对象的消除,同时保持代码的清晰性和可维护性。

http://www.dtcms.com/a/360444.html

相关文章:

  • centos 7 安装docker、docker-compose教程
  • AI 编程新玩法:用 yunqi-saas-kit 框架制作小游戏,看广告变现轻松赚钱​
  • 国产数据库之TiDB:博采众长
  • Ruoyi-vue-plus-5.x第二篇MyBatis-Plus数据持久层技术:2.2 分页与性能优化
  • [嵌入式embed]Keil5项目提示Missing: Compiler Version 5
  • 工业互联项目总结:UART
  • Backroom:信息代币化 AI 时代数据冗杂的解决方案
  • 漏洞基础与文件包含漏洞原理级分析
  • 使用 Python mlxtend库进行购物篮分析、关联规则
  • 软考中级习题与解答——第一章_数据结构与算法基础(3)
  • 进程状态 —— Linux内核(Kernel)
  • Linux 文件夹权限也会导致基本命令权限缺失问题
  • 【学Python自动化】 5. Python 数据结构学习笔记
  • postman带Token测试接口
  • 打工人日报#20250831
  • LangChain核心抽象:Runnable接口深度解析
  • * 和**有时展开,有时收集。*在对可迭代对象展开 **对字典展开。一般只看收集就够了,在函数定义的时候传入参数用
  • 第二十七天-ADC模数转换实验
  • linux系统学习(12.linux服务)
  • 【星闪】Hi2821 | SPI串行外设接口 + OLED显示屏驱动例程
  • 语音芯片3W输出唯创知音WTN6040FP、WT588F02BP-14S、WT588F04AP-14S
  • [回溯+堆优化]37. 解数独
  • Q1 Top IF 18.7 | 基于泛基因组揭示植物NLR进化
  • 高校心理教育辅导系统的设计与实现|基于SpringBoot高校心理教育辅导系统的设计与实现
  • 网格图--Day02--网格图DFS--面试题 16.19. 水域大小,LCS 03. 主题空间,463. 岛屿的周长
  • 技术总体方案设计思路
  • SAP报工与收货的区别(来自deepseek)
  • c++ 二维码、条形码开发实例
  • FFMPEG学习任务
  • 为什么计算机使用补码存储整数:补码的本质