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

时间:2012-06-05 13:01:12

标签: 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());
} ///:~

为什么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();

中复原

4 个答案:

答案 0 :(得分:32)

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

一个问题是,您可以在临时方法上调用方法:因此X(1).modify()很好但f7(X(1))不是。

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

因此,以下语句X(1).modify();可以完全转换为:

{
    X __0(1);
    __0.modify();
} // automatic cleanup of __0

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

{
    X __0(f5());
    X __1(1);
    __0.operator=(__1);
}

其他翻译正在交换__0__1初始化的顺序

它的关键是__0.operator=(__1)是一个方法调用,可以在临时函数上调用方法:)

答案 1 :(得分:11)

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

  

“更有效的C ++”,Scott Meyers。第19项:“了解其起源   临时对象“

。关于布鲁斯·埃克尔对“临时”的报道, 好吧,正如我怀疑和Christian Rau直接指出的那样,这很明显 错误!哎呀!他(Eckel's)用我们作为豚鼠! (这将是一个 一旦他纠正了他的所有错误,就像我这样的新手一本好书。)

  

Meyer:“C ++中的真正临时对象是不可见的 - 它们不是   出现在您的源代码中。每当非堆对象出现时它们就会出现   已创建但未命名。这些未命名的对象通常出现在其中一个中   两种情况:当应用隐式类型转换时   函数调用成功,函数返回对象时。“

     

“首先考虑创建临时对象的情况   使函数调用成功。当对象的类型发生时   传递给函数的函数与参数的类型不一样   它受到约束。“

     

“仅当按值或何时传递对象时,才会发生这些转换   传递给reference-to-const参数。它们不会发生在   将对象传递给引用到非const参数。“

     

“临时对象所处的第二种情况   created是在函数返回一个对象时。“

     

“任何时候你看到一个引用-conc参数,可能性   存在将创建临时将绑定到该参数。   只要你看到一个返回一个对象的函数,就会有一个临时的   创建(后来被销毁)。“

答案的另一部分见于:“Meyer:Effective C ++”,in “简介”:

  

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

String s1;       // call default constructor
String s2(s1);   // call copy constructor
String s3 = s2;  // call copy constructor
  

“复制构造函数最重要的用途可能就是定义   通过值传递和返回对象意味着什么。“

关于我的问题:

f5() = X(1) //what is happening?

这里没有初始化新对象,这不是 初始化(复制构造函数):它是一个赋值(as Matthieu M指出)。

临时创建是因为根据迈耶(上段), 两个函数都返回值,因此正在创建临时对象。 正如Matthieu指出使用伪代码,它变成: __0.operator=(__1)和按位复制(由...完成) 编译器)。

关于:

void f7(X& x);
f7(f5);
因此,暂时无法创建(Meyer:热门段落)。 如果它已被声明:void f7(const X& x);那么临时就会 已创建。

关于临时对象是常数:

迈耶说(和马蒂厄):“将创建一个临时的绑定 参数“。

因此临时只能绑定到常量引用,而不是 一个“const”对象。

关于: 什么是X(1)

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

  

“C风格的演员表看起来像这样:( T)表达式//演员表达式   类型T

     

函数式强制转换使用以下语法:T(表达式)//强制转换表达式   属于T“

所以X(1)是一个函数式转换。 1表达式被强制转换为 输入X

Meyer又说了一遍:

  

“关于我唯一一次使用旧式演员表是我想打电话的时候   显式构造函数将对象传递给函数。例如:

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));
  

不知何故,故意创造对象并不像演员那样“感觉”,所以我会   可能使用函数式转换而不是static_cast   这种情况。“

答案 2 :(得分:6)

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

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

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

    < / LI>

答案 3 :(得分:1)

以下是执行代码时实际发生的示例。我做了一些修改以澄清幕后的过程:

function met() {
    $("select[id*='outcome']")[0].selectedIndex = 2; # this works
        $("select[id*='outcome']").each(function() {
            $(this).selectedIndex = 2; # this doesn't
     });
}

您必须知道,在大多数现代编译器中,将避免复制构造。这是因为编译器进行了优化(返回值优化)。在我的输出中,我已经明确地删除了优化,以显示根据标准实际发生的事情。如果您想要删除此优化,请使用以下选项:

#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();
}