我正在阅读Eckel的常章,并在Temporaries被解释的部分混淆了。我能得到的是,当我们将引用传递给函数时,编译器会创建一个临时的const对象,因此即使我们将引用传递为
,我们也无法修改它。f(int &a){}
现在我试着在网上查看其他一些Temporaries的参考文献并且被卡住了
http://publib.boulder.ibm.com/infocenter/lnxpcomp/v8v101/index.jsp?topic=%2Fcom.ibm.xlcpp8l.doc%2Flanguage%2Fref%2Fcplr382.htm和
Are all temporaries rvalues in C++?
这促使我临时工具不仅仅是传递引用并在函数内部创建const对象。现在我可以从这两个链接中获得一些东西,但不能说我已经理解了临时工作的工作,功能和使用整个。如果有人可以解释临时的概念,那将会非常有用。在此先感谢。
布鲁斯埃克尔的原始例子是:
// Result cannot be used as an lvalue
class X {
int i;
public:
X(int ii = 0);
void modify();
};
X::X(int ii) { i = ii; }
void X::modify() { i++; }
X f5() {
return X();
}
const X f6() {
return X();
}
void f7(X& x) { // Pass by non-const reference
x.modify();
}
int main() {
f5() = X(1); // OK -- non-const return value
f5().modify(); // OK
// Causes compile-time errors:
//! f7(f5());
//! f6() = X(1);
//! f6().modify();
//! f7(f6());
} ///:~
答案 0 :(得分:2)
在C ++中,临时对象是编译器在各种上下文中创建的未命名对象。典型用法包括引用初始化,参数传递,表达式评估(包括标准类型转换),函数返回和异常(throw表达式)。
从此link开始:
创建临时对象以初始化引用变量时,临时对象的名称与引用变量的名称相同。在评估完整表达式(一个不是另一个表达式的子表达式的表达式)期间创建临时对象时,它将作为其评估中的最后一步被销毁,该步骤在词法上包含创建它的点。
破坏全表达式有例外:
如果为具有构造函数的类创建临时对象,则编译器调用相应的(匹配)构造函数来创建临时对象。
当销毁临时对象并且存在析构函数时,编译器会调用析构函数来销毁临时对象。当您从创建临时对象的作用域退出时,它将被销毁。如果引用绑定到临时对象,则当引用超出范围时,将销毁临时对象,除非它在控制流中断之前被销毁。例如,由构造函数初始值设定项为引用成员创建的临时对象在离开构造函数时被销毁。
如果此类临时对象是冗余的,则编译器不会构造它们,以便创建更有效的优化代码。在调试程序时,尤其是内存问题时,可能会考虑这种行为。
让我们总结一下this way。可以创建这些临时对象的原因如下:
使用与初始化的引用的基础类型不同类型的初始值设定项初始化const引用。
存储返回用户定义类型的函数的返回值。仅当程序不将返回值复制到对象时,才会创建这些临时值。由于返回值未复制到另一个对象,因此会创建一个临时对象。创建临时值的更常见情况是在评估必须调用重载的运算符函数的表达式期间。这些重载的操作符函数返回一个用户定义的类型,通常不会复制到另一个对象。
将演员表的结果存储到用户定义的类型。当给定类型的对象显式转换为用户定义的类型时,该新对象将构造为临时对象。
让我们考虑example:
class X {
/ / ...
public:
/ / ...
X(int);
X(const X&);
~X();
};
X f(X);
void g()
{
X a(1);
X b = f(X(2));
a = f(a);
}
此处,实现可能会使用临时构造X(2)
,然后使用f()
的复制构造函数将其传递给X
;或者,可以在用于保存参数的空间中构造X(2)
。此外,临时可能用于保存f(X(2))
的结果,然后使用X
的复制构造函数将其复制到b;或者,f()
的结果可能会在b
中构建。另一方面,表达式a=f(a)
需要f(a)
的结果临时,然后将其分配给a
。
答案 1 :(得分:2)
临时是一个未命名的对象(一些人的结果 表达式),并且始终是一个右值。或者也许应该 更好地说导致rvalue的表达式是 暂时的。
在C中,rvalues / temporaries不是真正的对象(从某种意义上说)
标准使用“对象”一词:找到的东西
在记忆中)。因此,例如,他们没有cv资格
(3 + 5
之类的表达式的类型为int
,而不是int const
,
和cv-qualifiers在函数返回值上被忽略)和你
不能拿他们的地址(因为他们不在记忆中,他们
没有地址)。在C ++中,问题是由类阴云密布
类型:你可以调用rvalue上的成员函数
成员函数将有一个this
指针,这意味着
甚至rvalues(类类型)必须在内存中有一个地址,和
cv-qualifications有意义,因为如果返回类型是
const
,你不能在它上面调用非const函数。
最后,虽然rvalue和temporary的概念是
非常密切相关,C ++标准使用的单词
方式略有不同。表达式的结果是
rvalue或左值(C ++ 11增加了其他可能性,
但你可以忽略它们,直到你成为专家),而这一点
区别涉及所有类型。当C ++标准谈到时
暂时的,它是一个rvalue,它是(或已成为)一个对象。
在大多数情况下,这些都有类型;很少
如果你有一个临时的不是类的情况
键入编写良好的代码,除模板之外
参与其中。区别很重要,因为即使是
内置&
运算符在rvalue类的rvalues上是非法的
type具有已定义的“生存期”和内存地址。那
生命是直到完整表达的结束。上课的时候
类型有关,临时和之间的区别
一个命名值主要是临时没有名字,
它有不同的寿命。类类型的生命周期
重要的有两个原因:第一,它确定何时
调用析构函数,其次,如果对象“泄漏”
指向内部数据的指针,例如就像std::string::c_str()
一样
确定此指针有效的时间。
最后,我提到模板,因为这是唯一的时间
你会有一个非类类型的const引用。通常
in参数的约定是非类的值传递
类型,以及类类型的const引用;作者
但是,模板不知道T
是否是一个类
类型与否,在大多数情况下,将定义他的功能
拿一个T const&
。在实践中,这将是唯一的
你最终会得到一个非类型的临时对象
(如果模板保存参数的地址,您可以
有问题)。
答案 2 :(得分:0)
让我们从一个例子开始:
float f(int a);
int g(float b);
int main() { int a=10; std::cout << g(f(a)); }
函数调用如下所示:
int ---f----> float ---g---> int
当编译器为g(f(a))生成代码时,需要一些内存来存储结果 调用f()后浮动。此内存区域称为临时区域。它是f()和g()之间的记忆。
答案 3 :(得分:0)
临时对象是计算的“副产品”。它们没有明确声明,顾名思义,它们是临时的。但是,您应该知道编译器何时创建临时对象,因为通常可以防止这种情况发生。
由于他的帖子中有几个答案详细介绍了临时工具,但我想补充一些与他们相关的“额外开销”,但也有办法可以避免它们。
临时出现的最常见位置是将对象按值传递给方法。形式参数在堆栈上创建。这可以通过使用传递地址或通过引用传递来防止。
编译器可以在分配对象时创建临时对象。例如,可以为int指定一个以int作为参数的构造函数。编译器将使用int作为参数创建临时对象,然后在对象上调用赋值运算符。您可以通过在构造函数的声明中使用explicit关键字来阻止编译器在您的背后执行此操作。
当按值返回对象时,通常会使用临时对象。必须返回对象的方法通常必须创建要返回的对象。由于构造此对象需要时间,因此我们希望尽可能避免使用它。有几种方法可以实现这一目标。
3.A。而不是返回一个对象,而是向该方法添加另一个参数,该参数允许程序员传入程序员想要存储结果的对象。这样,该方法就不必创建额外的对象。它将简单地使用传递给方法的参数。这种技术称为返回值优化(RVO)。
RVO是否会导致实际优化取决于编译器。不同的编译器处理方式不同。帮助编译器的一种方法是使用计算构造函数。可以使用计算构造函数来代替返回对象的方法。计算构造函数采用与要优化的方法相同的参数,但不是基于参数返回对象,而是根据参数的值初始化自身。
使用=运算符可以避免临时值。例如,代码
a = b + c; 可写成 A = B; 一个+ = C;