临时对象 - 何时创建,如何在代码中识别它们?

33 c++ temporary-objects

在Eckel,第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());
} ///:~
Run Code Online (Sandbox Code Playgroud)

为什么要f5() = X(1)成功?这里发生了什么???

Q1.他X(1)什么时候 - 这里发生了什么?这是一个构造函数调用 - 不应该这样读X::X(1);它是类实例化 - 不是类实例化类似于:X a(1);编译器如何确定是什么 X(1)?我的意思是..名称装饰发生所以.. X(1)构造函数调用将转换为如下:globalScope_X_int作为函数名称.. ???

Q2.当然,临时对象用于存储X(1) 创建的结果对象,然后不会将其分配给对象f5()返回(也可能是临时对象)?鉴于f5()返回一个即将被丢弃的临时对象,他怎么能将一个常量临时赋值给另一个常量临时?有人可以清楚地解释为什么: f7(f5());应该在一个不变的临时而不是普通的老年人中复习f5();

Mat*_* M. 33

你的所有问题都归结为C++中的一条规则,即一个临时对象(没有名字的对象)不能绑定到非const引用.(因为Stroustrup认为它可能引发逻辑错误......)

一个问题是,你可以在临时调用方法:所以X(1).modify()很好但f7(X(1))不是.

至于创建临时的位置,这是编译器作业.语言的规则精确地说临时应该只存在到当前的完整表达式结束之前(并且不再存在),这对于析构函数具有副作用的类的临时实例很重要.

因此,以下声明X(1).modify();可以完全翻译为:

{
    X __0(1);
    __0.modify();
} // automatic cleanup of __0
Run Code Online (Sandbox Code Playgroud)

考虑到这一点,我们可以攻击f5() = X(1);.我们这里有两个临时工,还有一个任务.在调用赋值之前,必须完全评估赋值的两个参数,但顺序不准确.一种可能的翻译是:

{
    X __0(f5());
    X __1(1);
    __0.operator=(__1);
}
Run Code Online (Sandbox Code Playgroud)

(另一个翻译是交换订单__0__1初始化)

它工作的关键__0.operator=(__1)是方法调用,并且可以在临时工具上调用方法:)

  • @paleywiener 临时对象 ** 不是 ** 一个常量对象,这一直是我们的答案。如果这就是 *Eckel* 所说的(尽管我对此表示怀疑),那么他就是**错**。 (2认同)

小智 15

我对答案并不完全满意,所以我看了看:

"更有效的C++",Scott Meyers.第19项:"了解临时物体的起源"

.关于布鲁斯·埃克尔对"临时主义者"的报道,正如我怀疑和正如克里斯蒂安·劳直接指出的那样,这是完全错误的!哎呀!他(Eckel's)用我们作为豚鼠!(一旦他纠正了他的所有错误,对我这样的新手来说这将是一本好书)

Meyer:"C++中的真正临时对象是不可见的 - 它们不会出现在您的源代码中.它们会在创建非堆对象但未命名时出现.这些未命名的对象通常出现在以下两种情况之一:隐式类型转换时用于使函数调用成功以及函数返回对象时."

"首先考虑创建临时对象以使函数调用成功的情况.当传递给函数的对象类型与其绑定的参数类型不同时,就会发生这种情况."

"这些转换仅在按值传递对象或传递给引用到const参数时发生.在将对象传递给引用到非const参数时不会发生这种转换."

"创建临时对象的第二组情况是函数返回一个对象."

"无论何时你看到一个引用到const的参数,都有可能创建一个临时的绑定到该参数.当你看到一个函数返回一个对象时,就会创建一个临时的(后来被销毁)."

答案的另一部分见于:"Meyer:Effective C++",在"简介"中:

"复制构造函数用于初始化具有相同类型的不同对象的对象:"

String s1;       // call default constructor
String s2(s1);   // call copy constructor
String s3 = s2;  // call copy constructor
Run Code Online (Sandbox Code Playgroud)

"复制构造函数最重要的用途可能是定义按值传递和返回对象的含义."

关于我的问题:

f5() = X(1) //what is happening?
Run Code Online (Sandbox Code Playgroud)

这里没有初始化一个新对象,这不是初始化(复制构造函数):它是一个赋值(正如Matthieu M指出的那样).

临时创建是因为根据Meyer(顶部段落),两个函数都返回值,因此正在创建临时对象.正如Matthieu指出使用伪代码一样,它变为: __0.operator=(__1)并且发生按位复制(由编译器完成).

关于:

void f7(X& x);
f7(f5);
Run Code Online (Sandbox Code Playgroud)

ergo,暂时不能创建(Meyer:顶部段落).如果它已被声明:void f7(const X& x);那么就会创建一个临时的.

关于临时对象是常数:

Meyer说它(和Matthieu):"将创建一个临时的绑定到该参数."

因此临时仅绑定到常量引用,并且本身不是"const"对象.

关于:什么是X(1)

Meyer,Item27,Effective C++ - 3e,他说:

"C风格的强制转换看起来像这样:( T)表达式//将表达式转换为T类型

函数式转换使用以下语法:T(表达式)//将表达式转换为类型T"

所以X(1)是一个函数样式转换.1表达式被强制转换为类型X.

梅耶又说了一遍:

"关于我使用旧式强制转换的唯一一次是当我想调用显式构造函数将对象传递给函数时.例如:

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));
Run Code Online (Sandbox Code Playgroud)

不知何故,有意识的对象创建并不像演员一样"感觉",所以在这种情况下我可能会使用函数式转换而不是static_cast."


Chr*_*ica 6

  1. 这确实是一个构造函数调用,一个表达式,用于评估类型的临时对象X.形式的表达X([...])X作为一个类型的名称是创建类型的临时对象的构造函数调用X(虽然我不知道该怎么解释,在适当的standardese,有特殊情况下,解析器可以表现不同).这与您在函数f5f6函数中使用的构造相同,只是省略了可选ii参数.

  2. X(1)生命中创建的临时(不会被破坏/无效)直到包含它的完整表达式结束,这通常意味着(就像在这种情况下使用赋值表达式)直到分号.同样,f5创建一个临时的X并将其返回到调用站点(内部main),从而复制它.所以在main中,f5调用也返回一个临时的X.此临时X然后被分配临时X的创建X(1).在完成之后(如果你想要分号到达),两个临时都会被摧毁.这个赋值是有效的,因为这些函数返回普通的非常量对象,无论它们是在表达式被完全评估后是否只是临时的和被破坏的(因此,即使完全有效,也可以使赋值或多或少无意义).

    它不起作用,f6因为它返回一个const X你无法分配的.同样不起作用f7(f5()),因为f5创建临时和临时对象不绑定到非const左值引用X&(C++ 11 X&&为此目的引入了右值引用,但这是一个不同的故事).如果f7采用const引用const X&,它将起作用,因为常量左值引用绑定到临时值(f7当然,然后它本身将不再起作用).

  • @paleywiener不,它们**不是**自动常量,这就是这个分配起作用的原因(以及为什么有一个额外的函数“f6”返回一个“const”,其中“f5”和“f6”之间的区别`否则呢?)。我不知道这是否是断章取义,但你的引用完全是错误的。或者也许他只是建议您自己将所有返回值设为常量。 (2认同)