C++ 临时对象——它们是什么时候创建的,你如何在代码中识别它们?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10897799/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Temporary objects - when are they created, how do you recognise them in code?
提问by
In Eckel, Vol 1, pg:367
在埃克尔,第 1 卷,第 367 页
//: C08:ConstReturnValues.cpp
// Constant return by value
// 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());
} ///:~
Why does f5() = X(1)
succed? What is going on here???
为什么f5() = X(1)
成功?这里发生了什么???
Q1. When he does X(1)
- what is going on here? Is this a constructor call -
shouldn't this then read X::X(1);
Is it class instantiation - isn't class
instantiation something like: X a(1);
How does the compiler determine what
X(1)
is?? I mean.. name decoration takes place so.. X(1)
the constructor
call would translate to something like: globalScope_X_int
as the function
name.. ???
一季度。当他这样做时X(1)
- 这里发生了什么?这是一个构造函数调用 - 不应该然后读取X::X(1);
它是类实例化 - 类实例化不是类似于:X a(1);
编译器如何确定是什么
X(1)
??我的意思是.. 名称修饰发生这样..X(1)
构造函数调用将转换为类似:globalScope_X_int
作为函数名称.. ???
Q2. Surely a temporary object is used to store the resulting object that X(1)
creates and then would't that be then assigned to the object f5()
returns
(which would also be a temporary object)? Given that f5()
returns a temporary
object that will be soon be discarded, how can he assign one constant temporary
to another constant temporary??? Could someone explain clearly why:
f7(f5());
should reult in a constant temporary and not plain old f5();
Q2。当然,临时对象用于存储X(1)
创建的结果对象,然后将其分配给对象f5()
返回(这也将是临时对象)?鉴于f5()
返回一个即将被丢弃的临时对象,他如何将一个临时常量分配给另一个临时常量???有人可以解释清楚为什么:
f7(f5());
应该导致一个持续的临时而不是简单的旧f5();
采纳答案by Matthieu M.
I wasn't entirely satisfied by the answers, so I took a look at:
我对答案并不完全满意,所以我看了一下:
"More Effective C++", Scott Meyers. Item 19: "Understand the origin of temporary Objects"
“更有效的 C++”,斯科特·迈耶斯。第 19 项:“了解临时对象的起源”
. Regarding Bruce Eckel's coverage of "Temporaries", well, as I suspect and as Christian Rau directly points out, it's plain wrong! Grrr! He's (Eckel's) using us as guinea pigs!! (it would be a good book for newbies like me once he corrects all his mistakes)
. 关于布鲁斯·埃克尔对“临时工”的报道,正如我怀疑的那样,正如克里斯蒂安·劳直接指出的那样,这是完全错误的!呸!他(埃克尔的)把我们当作豚鼠!!(一旦他纠正了所有错误,这对于像我这样的新手来说将是一本好书)
Meyer: "True temporary objects in C++ are invisible - they don't appear in your source code. They arise whenever a non-heap object is created but not named. Such unnamed objects usually arise in one of two situations: when implicit type conversions are applied to make function calls succeed and when functions return objects."
"Consider first the case in which temporary objects are created to make function calls succeed. This happens when the type of object passed to a function is not the same as the type of the parameter to which it is being bound."
"These conversions occur only when passing objects by value or when passing to a reference-to-const parameter. They do not occur when passing an object to a reference-to-non-const parameter."
"The second set of circumstances under which temporary objects are created is when a function returns an object."
"Anytime you see a reference-to-const parameter, the possibility exists that a temporary will be created to bind to that parameter. Anytime you see a function returning an object, a temporary will be created (and later destroyed)."
Meyer:“C++ 中真正的临时对象是不可见的——它们不会出现在你的源代码中。只要创建了一个非堆对象但没有命名它们就会出现。这种未命名的对象通常出现在两种情况之一:当隐式类型转换时用于使函数调用成功以及函数返回对象时。”
“首先考虑创建临时对象以使函数调用成功的情况。当传递给函数的对象类型与其绑定到的参数类型不同时,就会发生这种情况。”
“这些转换仅在按值传递对象或传递给引用常量参数时发生。将对象传递给非常量引用参数时不会发生这些转换。”
“创建临时对象的第二组情况是函数返回对象时。”
“任何时候你看到一个对 const 参数的引用,就有可能会创建一个临时对象来绑定到那个参数。任何时候你看到一个函数返回一个对象,一个临时对象就会被创建(然后被销毁)。”
The other part of the answer is found in: "Meyer: Effective C++", in the "Introduction":
答案的另一部分见:“Meyer: Effective C++”,在“Introduction”中:
"a copy constructor is used to initialize an object with a different object of the same type:"
“复制构造函数用于初始化具有相同类型的不同对象的对象:”
String s1; // call default constructor
String s2(s1); // call copy constructor
String s3 = s2; // call copy constructor
"Probably the most important use of the copy constructor is to define what it means to pass and return objects by value."
“可能复制构造函数最重要的用途是定义按值传递和返回对象的含义。”
Regarding my questions:
关于我的问题:
f5() = X(1) //what is happening?
Here a new object isn't being initialized, ergo this is not initialization(copy constructor): it's an assignment (as Matthieu M pointed out).
这里没有初始化一个新对象,因此这不是初始化(复制构造函数):它是一个赋值(正如 Matthieu M 指出的那样)。
The temporaries are created because as per Meyer (top paragraphs),
both functions return values, so temporary objects are being created.
As Matthieu pointed out using pseudo-code, it becomes:
__0.operator=(__1)
and a bitwise copy takes place(done by the
compiler).
创建临时对象是因为根据 Meyer(顶部段落),两个函数都返回值,因此正在创建临时对象。正如 Matthieu 使用伪代码指出的那样,它变成:
__0.operator=(__1)
并且发生按位复制(由编译器完成)。
Regarding:
关于:
void f7(X& x);
f7(f5);
ergo, a temporary cannot be created (Meyer: top paragraphs).
If it had been declared: void f7(const X& x);
then a temporary would
have been created.
因此,无法创建临时文件(Meyer:顶部段落)。如果已声明:void f7(const X& x);
那么将创建一个临时文件。
Regarding a temporary object being a constant:
关于作为常量的临时对象:
Meyer says it (and Matthieu): "a temporary will be created to bind to that parameter."
Meyer(和 Matthieu)说:“将创建一个临时对象来绑定到该参数。”
So a temporary is only bound to a constant reference and is itself not a "const" object.
所以临时对象只绑定到一个常量引用,它本身不是一个“const”对象。
Regarding:
what is X(1)
?
关于:什么是X(1)
?
Meyer, Item27, Effective C++ - 3e, he says:
Meyer,Item27,Effective C++ - 3e,他说:
"C-style casts look like this: (T)expression //cast expression to be of type T
Function-style casts use this syntax: T(expression) //cast expression to be of type T"
"C 风格的转换看起来像这样: (T)expression //将表达式转换为 T 类型
函数式转换使用以下语法: T(expression) //将表达式转换为 T 类型"
So X(1)
is a function-style cast. 1
the expression is being cast to
type X
.
X(1)
函数式类型转换也是如此。1
表达式被强制转换为 type X
。
And Meyer says it again:
迈耶又说了一遍:
"About the only time I use an old-style cast is when I want to call an explicit constructor to pass an object to a function. For example:
“我唯一一次使用旧式强制转换是当我想调用显式构造函数将对象传递给函数时。例如:
class Widget {
public:
explicit Widget(int size);
...
};
void doSomeWork(const Widget& w);
doSomeWork(Widget(15)); //create Widget from int
//with function-style cast
doSomeWork(static_cast<Widget>(15));
Somehow, deliberate object creation doesn't "feel" like a cast, so I'd probably use the function-style cast instead of the static_cast in this case."
不知何故,故意创建对象并不“感觉”像强制转换,因此在这种情况下,我可能会使用函数风格的强制转换而不是 static_cast。”
回答by Matthieu M.
All your questions boil down to a rule in C++ which says that a temporary object (one that has no name) cannot be bound to a non-const reference. (Because Stroustrup felt it could provoke logical errors...)
您所有的问题都归结为 C++ 中的一条规则,即临时对象(没有名称的对象)不能绑定到非常量引用。(因为 Stroustrup 觉得这可能会引发逻辑错误......)
The one catch, is that you can invoke a method on a temporary: so X(1).modify()
is fine but f7(X(1))
is not.
一个问题是,您可以在临时调用方法:这样X(1).modify()
很好,但f7(X(1))
不是。
As for where the temporary is created, this is the compiler job. The rules of the language precise that the temporary should only survive until the end of the current full-expression (and no longer) which is important for temporary instances of classes whose destructor has a side-effect.
至于临时在哪里创建,这是编译器的工作。该语言的规则明确指出,临时对象应该只存在到当前完整表达式的结尾(并且不再存在),这对于析构函数具有副作用的类的临时实例很重要。
Therefore, the following statement X(1).modify();
can be fully translated to:
因此,以下语句X(1).modify();
可以完全翻译为:
{
X __0(1);
__0.modify();
} // automatic cleanup of __0
With that in mind, we can attack f5() = X(1);
. We have two temporaries here, and an assignment. Both arguments of the assignment must be fully evaluated before the assignment is called, but the order is not precise. One possible translation is:
考虑到这一点,我们可以攻击f5() = X(1);
. 我们这里有两个临时工和一个任务。在调用赋值之前,必须完全评估赋值的两个参数,但顺序并不精确。一种可能的翻译是:
{
X __0(f5());
X __1(1);
__0.operator=(__1);
}
(the other translation is swapping the order in which __0
and __1
are initialized)
(另一个翻译是交换__0
和__1
初始化的顺序)
And the key to it working is that __0.operator=(__1)
is a method invocation, and methods can be invoked on temporaries :)
它工作的关键__0.operator=(__1)
是方法调用,方法可以在临时对象上调用:)
回答by Christian Rau
This is indeed a constructor call, an expression evaluating to a temporary object of type
X
. Expressions of the formX([...])
withX
being the name of a type are constructor calls that create temporary objects of typeX
(though I don't know how to explain that in proper standardese, and there are special cases where the parser can behave differently). This is the same construct you use in yourf5
andf6
functions, just omitting the optionalii
argument.The temporary created by
X(1)
lives (doesn't get destructed/invalid) until the end of the full expression containing it, which usually means (like in this case with the assignment expression) until the semicolon. Likewise doesf5
create a temporaryX
and return it to the call site (insidemain
), thus copying it. So in main thef5
call also returns a temporaryX
. This temporaryX
is then assigned the temporaryX
created byX(1)
. After that is done (and the semicolon reached, if you want), both temporaries get destroyed. This assignment works because those functions return ordinary non-constantobjects, no matter if they are just temprorary and destroyed after the expression is fully evaluated (thus making the assignment more or less senseless, even though perfectly valid).It doesn't work with
f6
since that returns aconst X
onto which you cannot assign. Likewise doesf7(f5())
not work, sincef5
creates a temporary and temporary objects don't bind to non-const lvalue referencesX&
(C++11 introduced rvalue referencesX&&
for this purpose, but that's a different story). It would work iff7
took a const referenceconst X&
, as constant lvalue references bind to temporaries (but thenf7
itself wouldn't work anymore, of course).
这确实是一个构造函数调用,一个计算类型为临时对象的表达式
X
。形式的表达X([...])
与X
作为一个类型的名称是创建类型的临时对象的构造函数调用X
(虽然我不知道该怎么解释,在适当的standardese,有特殊情况下,解析器可以表现不同)。这与您在f5
和f6
函数中使用的构造相同,只是省略了可选ii
参数。由
X(1)
生命创建的临时对象(不会被破坏/无效)直到包含它的完整表达式结束,这通常意味着(就像在这种情况下使用赋值表达式)直到分号。同样确实f5
创建一个临时X
并将其返回到调用站点(内部main
),从而复制它。所以在 main 中,f5
调用也返回一个临时的X
。X
然后将此临时分配给由X
创建的临时X(1)
。完成后(如果需要,可以到达分号),两个临时文件都会被销毁。这个赋值有效是因为这些函数返回普通的非常量对象,无论它们是否只是临时的并且在表达式被完全评估后被销毁(因此使赋值或多或少毫无意义,即使完全有效)。它不起作用,
f6
因为它返回一个const X
你不能分配的。同样不起作用f7(f5())
,因为f5
创建临时对象和临时对象不会绑定到非常量左值引用X&
(C++11X&&
为此目的引入了右值引用,但那是另一回事)。如果f7
采用 const 引用const X&
,它将起作用,因为常量左值引用绑定到临时对象(但f7
当然,它本身将不再起作用)。
回答by stanimirco
Here is an example what actually happens when you execute your code. I've made some modifications to clarify the processes behind the scene:
这是执行代码时实际发生的示例。我进行了一些修改以阐明幕后的流程:
#include <iostream>
struct Object
{
Object( int x = 0 ) {std::cout << this << ": " << __PRETTY_FUNCTION__ << std::endl;}
~Object() {std::cout << this << ": " << __PRETTY_FUNCTION__ << std::endl;}
Object( const Object& rhs ){std::cout << this << ": " << __PRETTY_FUNCTION__ << " rhs = " << &rhs << std::endl;}
Object& operator=( const Object& rhs )
{
std::cout << this << ": " << __PRETTY_FUNCTION__ << " rhs = " << &rhs << std::endl;
return *this;
}
static Object getObject()
{
return Object();
}
};
void TestTemporary()
{
// Output on my machine
//0x22fe0e: Object::Object(int) -> The Object from the right side of = is created Object();
//0x22fdbf: Object::Object(int) -> In getObject method the Temporary Unnamed object is created
//0x22fe0f: Object::Object(const Object&) rhs = 0x22fdbf -> Temporary is copy-constructed from the previous line object
//0x22fdbf: Object::~Object() -> Temporary Unnamed is no longer needed and it is destroyed
//0x22fe0f: Object& Object::operator=(const Object&) rhs = 0x22fe0e -> assignment operator of the returned object from getObject is called to assigne the right object
//0x22fe0f: Object::~Object() - The return object from getObject is destroyed
//0x22fe0e: Object::~Object() -> The Object from the right side of = is destroyed Object();
Object::getObject() = Object();
}
You have to know that on most modern compilers the copy construction will be avoided. This is because the optimization which is made (Return Value Optimization) by the compiler. In my output I have removed explicitly the optimization to show what actually happens according to the standard. If you want to remove this optimization too use the following option:
您必须知道,在大多数现代编译器中,将避免复制构造。这是因为编译器进行了优化(返回值优化)。在我的输出中,我已经明确删除了优化以显示根据标准实际发生的情况。如果您也想删除此优化,请使用以下选项:
-fno-elide-constructors