More Effective C++ 读书笔记六——临时对象

条款19:了解临时对象的来源

c++真正的所谓的临时对象是不可见的——不会再你的源代码中出现。此等匿名对象通常发生于两种情况:一是当隐式类型转换(implicit type conveersions)被施行起来以求函数调用能够成功;二是当函数返回对象的时候。

第一种情况的例子:
[cce lang=”cpp”]
#include <iostream>

class Int
{
public:
Int(int value) {
_value = value;
std::cout << "In constructor, count: " << ++count << std::endl;
}
~Int() {
std::cout << "In destructor\n";
}

int _value;
private:
static int count;
};

int Int::count = 0;

void printInt(Int intValue) {
std::cout << "int value is: " << intValue._value << std::endl;
}

int main()
{
printInt(10);
return 0;
}
[/cce]
这个例子的运行结果是:
In constructor, count: 1
int value is: 10
In destructor
验证了编译器再发现没法调用printInt(int)的时候,会进行自动类型转换,通过Int的构造函数,将int隐式转换成Int(这个貌似之前也提到过了)。这个构造出来的就是所谓的临时对象,从输出的顺序看,这个对象在函数返回之后被析构。

只有当对象以by value(传值)方式传递,或是当对象被传递给一个reference-to -const参数时,这些转换才会发生。如果参数被传递给一个reference-to-non-const参数,并不会发生此类转换

也就是说,把上面的printInt入参改成Int&的时候,就无法调用成功。原因很简单了,这个临时对象,如果没修改,函数返回之后也被析构了,没法再次获取到,即使允许也是没意义的。改成Int&之后,编译器(g++)有这样的报错:
tmp.cpp: 在函数‘int main()’中:
tmp.cpp:27:13: 错误:用类型为‘int’的右值初始化类型为‘Int&’的非常量引用无效
tmp.cpp:21:6: 错误:在传递‘void printInt(Int&)’的第 1 个实参时

条款21:利用重载(overload)避免隐式类型转换(implicit type conversions)

这里介绍的就是通过重载,避免编译器自动通过构造函数创建临时对象,但是重载太多后面的维护什么的成本也会上升,需要考虑这个的必要性。

每个“重载操作符”必须获得至少一个“用户定制类型”的自变量

条款20:协助完成返回值优化(RVO)

另一个容易产生临时对象的地方就是函数返回值。不过经过尝试,发现目前编译器(g++)可以把命名变量也通过返回值优化去除。
也就是说,这样写:
[cce lang=”cpp”]
#include <iostream>

class Int
{
public:
Int(int value) {
_value = value;
std::cout << "In constructor, count: " << ++count << " value: " << _value <<std::endl;
}
Int(const Int &rhs) {
_value = rhs._value;
std::cout << "In copy constructor, count: " << ++count << " value: " << _value << std::endl;
}

friend const Int operator+(const Int &lhs, const Int &rhs);

private:
int _value;
static int count;
};

int Int::count = 0;
const Int operator+(const Int &lhs, const Int &rhs) {
Int result(lhs._value + rhs._value);
return result;
}

int main()
{
Int a(1);
Int b(2);
Int c = a + b;

return 0;
}
[/cce]
和这样写:
[cce lang=”cpp”]
#include <iostream>

class Int
{
public:
Int(int value) {
_value = value;
std::cout << "In constructor, count: " << ++count << " value: " << _value <<std::endl;
}
Int(const Int &rhs) {
_value = rhs._value;
std::cout << "In copy constructor, count: " << ++count << " value: " << _value << std::endl;
}

friend const Int operator+(const Int &lhs, const Int &rhs);

private:
int _value;
static int count;
};

int Int::count = 0;
const Int operator+(const Int &lhs, const Int &rhs) {
return Int(lhs._value + rhs._value);
}

int main()
{
Int a(1);
Int b(2);
Int c = a + b;

return 0;
}
[/cce]
最终的执行结果都是一样的:
In constructor, count: 1 value: 1
In constructor, count: 2 value: 2
In constructor, count: 3 value: 3
没有因为前者在operator+中多了一个result对象而多一个临时变量。

转载自:https://coolex.info/blog/276.html

时间: 2025-01-20 23:31:24

More Effective C++ 读书笔记六——临时对象的相关文章

More Effective C++ 读书笔记五——异常

条款12:了解"抛出一个exception"与"传递一个参数"或"调用一个虚函数"之间的差异 第一,exception object总是会被复制,如果以by value方式捕捉,它们甚至被复制两次.至于传递给函数参数的对象不一定得复制.第二,"被抛出成为exceptions"的对象,其被允许的类型转换动作,比"被传递到函数去"的对象少.第三,catch子句以其"出现于源代码的顺序"被编译

More Effective C++ 读书笔记二

条款4:非必要不提供default constructor 这里主要是列举下默认构造函数的优点和缺点. 如果没有默认构造函数,定义对象数组会比较麻烦,因为对象数组初始化的时候没法传递非默认构造函数的值,如果要使用,书中提到的方法是给数组每个变量初始化的时候调用构造函数,另一个就是使用指针数组. 第一个的缺点很明显,没法声明类似A a[10];这样的数组,在堆上申请,还得用到placement new这个之前没讲过的东西,另外还得一个个去初始化:后者的缺点当然是,数组里面的每个指针都需要记得去de

Effective C++ 读书笔记之Part1.Accustoming Yourself to C++

1.View C++ as a federation of languages C++的四个次语言: 1)C 2)Object-Oriented C++ 3)Template C++ 4)STL 2.Prefer consts, enums, and inlines to #defines 一方面是因为使用宏定义不利于调试的时候定位错误,另一方面主要是因为预处理器和编译器两者分工不同所导致的. 同时,宏定义太复杂的情况下很容易出现错误. 总结: 第一,对于单纯常量,最好以const对象或enum

Effective C++ 读书笔记之Part6.Inheritance and Object-Oriented Design

32.Make sure public inheritance models "is-a". 所谓的最佳设计,取决于系统希望做什么事,包括现在与未来. 需要解决的问题:其中关于两个assert都通过的地方有些疑惑. 总结: "public继承"意味着is-a.适用于base classes身上的每一件事情一定也适用于derived classes身上,因为每一个derived class对象也都是一个base class对象. 33.Avoid hiding inh

More Effective C++ 读书笔记四——异常

条款9:利用destructors避免泄漏资源 这里开始介绍了auto_ptr,其实就是利用了c++局部对象在离开作用域的时候,其析构函数会被调用,来避免资源泄漏.这样的好处,就是不管是作用域正常结束(跑出代码块)还是异常结束(抛出异常),对象的析构函数都能保证被调用. 条款10:在constructors内阻止资源泄漏 c++只会析构已构造完成的对象.对象只有在其constructor执行完毕才算是完全构造妥当. 也就是说,c++不自动清理那些"构造期间抛出exceptions"的对

More Effective C++ 读书笔记三

条款8:了解各种不同意义的new和delete 这里讲了3种new,分别是:new operator, operator new, placement new. new operator最简单,它就是我们平时常用的new关键字,需要注意的是,它是不能被重载的.new operator的语义是先分配内存,然后调用对象的构造函数. operator new:这个是这三个new里面唯一能够重载的,平时我们重载的就是这个操作符.它的声明是: [cc lang="cpp"] void *oper

Effective C++ 读书笔记之Part2.Constructors, Destructors, and Assignment Operators

5.Know what functions C++ silently writes and calls. 总结:编译器可以暗自为class创建default构造函数.copy构造函数.copy assginment操作符,以及析构函数.这些函数都是public的,并且是inline的. 6.Explicitly disallow the use of compiler-generated functions you to not want. 总结:为驳回编译器自动(暗自)提供的机能,可将相应的成

Effective C++ 读书笔记之Part5.Implementations

 26. Postpone variable definitions as long as possible. 总结: 尽可能延后变量定义式的出现.这样做可增加程序的清晰度并改善程序效率. 批注: 纯C语言此处有冲突,C语言要求变量定义出现在代码快的开始部分. 27. Minimize casting. 1)const_cast 通常被用来将对象的常量性转除(cast away the constness) .它也是唯一有此能力的 C++-style 转型操作符. 2)dynamic_cast主

Effective C++ 读书笔记之Part8.Customizing new and delete

49. Understand the behavior of the new-handler. 总结: 第一,set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用. 第二,Nothrow new是一个颇为局限的工具,因为它只适用于内存分配:后继的构造函数调用还是可能抛出异常. 50. Understand when it makes sense to replace new and delete. 替换编译器提供的operator new或operator dele