什么是С++临时对象?

我正在阅读埃克尔的常规章节,并在临时解释部分混淆了。 我可以得到的是,当我们将引用传递给一个函数时,编译器会创建一个临时对象,因此即使我们将引用作为参数传递,我们也不能修改它

f(int &a){}

现在我试着在网上查看一些其他的临时参考文献,并坚持下去

http://publib.boulder.ibm.com/infocenter/lnxpcomp/v8v101/index.jsp?topic=%2Fcom.ibm.xlcpp8l.doc%2Flanguage%2Fref%2Fcplr382.htm和

C ++中所有的临时对象都是rvalues吗?

这促使我临时工不仅仅是为函数内部传递引用和创建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());
} ///:~

在C ++中,临时对象是编译器在各种上下文中创建的未命名对象。 典型的用途包括参考初始化,参数传递,表达式评估(包括标准类型转换),函数返回和异常(throw表达式)。

从这个链接:

当创建临时对象以初始化引用变量时,临时对象的名称与引用变量的名称具有相同的作用域。 在评估完整表达式(不是另一个表达式的子表达式)的表达式的评估期间创建临时对象时,它会作为其评估的最后一步而被销毁,该步骤在词法上包含创建点的位置。

破坏全表达式有例外:

  • 该表达式作为定义对象的声明的初始化程序出现:当初始化完成时,临时对象被销毁。
  • 引用绑定到一个临时对象:临时对象在引用的生命周期结束时被销毁。
  • 如果为具有构造函数的类创建临时对象,则编译器会调用适当的(匹配)构造函数来创建临时对象。

    当临时对象被销毁并且析构函数存在时,编译器调用析构函数来销毁临时对象。 当您退出创建临时对象的作用域时,它将被销毁。 如果引用绑定到临时对象,则当引用超出作用域时临时对象将被销毁,除非它早于控制流程中断而被销毁。 例如,由构造函数初始化程序为引用成员创建的临时对象在离开构造函数时被销毁。

    在这种临时对象是多余的情况下,编译器不会构造它们,以便创建更高效​​的优化代码。 当您正在调试程序时,这种行为可能是一个考虑因素,特别是内存问题。

    让我们以这种方式总结一下。 可以创建这些临时对象的原因如下:

  • 用初始化器初始化一个类型与初始化引用的基础类型不同的初始化器。

  • 存储返回用户定义类型的函数的返回值。 这些临时对象只有在程序没有将返回值复制到对象时才会创建。 由于返回值不会复制到另一个对象,所以会创建一个临时对象。 创建临时对象的更常见情况是在评估表达式时必须调用重载操作符函数。 这些重载的运算符函数返回一个用户定义的类型,通常不会复制到另一个对象。

  • 将转换结果存储到用户定义的类型。 当给定类型的对象被显式转换为用户定义的类型时,该新对象被构造为临时对象。

  • 我们来看一个例子:

    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的复制构造函数将其传递给f()之前构造X(2) ; 或者, X(2)可能会在用于保存参数的空间中构建。 另外,在使用X的复制构造函数将f(X(2))的结果复制到b之前,可能会使用临时值来保存结果; 或者, f()的结果可能在b构造。 另一方面,表达式a=f(a)需要a=f(a)的结果暂时f(a) ,然后将其分配给a


    临时是一个未命名的对象(一些表达式的结果),并且总是一个右值。 或者,也许应该更好地说一个导致右值的表达式是暂时的。

    在C中,右值/临时值不是真正的对象(在这个意义上,标准使用单词“object”:位于内存中的东西)。 因此,例如,它们不是cv限定的(类似3 + 5的表达式具有int类型,而不是int const ,并且cv-qualifiers在函数返回值上被忽略),并且您不能接收它们的地址(因为它们不在记忆中,他们没有地址)。 在C ++中,这个问题被类类型覆盖了:你可以在右值上调用一个成员函数,并且该成员函数将有一个this指针,这意味着即使是类类型的右值也必须在内存中有一个地址, cv-qualifications具有含义,因为如果返回类型为const ,则不能在其上调用非const函数。

    最后,虽然右值和临时值的概念非常紧密相关,但C ++标准的用法略有不同。 表达式的结果要么是右值要么是左值(C ++ 11增加了其他可能性,但在专家之前您可以忽略它们),并且这种区别涉及所有类型。 当C ++标准谈到临时性时,它是一个正在(或已经成为)对象的右值。 大多数情况下,这些都是班级类型; 除非涉及到模板,否则极少有情况下,您将拥有临时文件,而且文件中的代码没有写入类型。 区别非常重要,因为即使内置的&运算符在右值非法,类类型的右值也会有一个定义的“生命期”和一个内存地址。 那一生直到完整的表达式结束。 因此,当涉及类类型时,临时名称和命名值之间的差异主要在于临时名称不具有名称,并且具有不同的生命周期。 类类型的生命周期很重要,原因有两个:首先,它决定何时调用析构函数;其次,如果对象“泄漏”了指向内部数据的指针,例如像std::string::c_str()确定这个指针可能有效的时间。

    最后,我提到了模板,因为这是关于唯一一次你有一个非类类型的const引用。 在参数中通常的惯例是非类类型的值传递,类类型的常量引用; 然而,模板的作者并不知道T是否是一个类的类型,在大多数情况下,它将定义他的函数来取一个T const& 。 在实践中,这将是唯一一次最终得到非类类型的临时对象(如果模板保存了参数的地址,则可能有问题)。


    让我们从一个例子开始:

    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()被调用后,它需要一些内存来存储结果的float。 这个存储区被称为临时存储区。 它是f()和g()之间的内存。

    链接地址: http://www.djcxy.com/p/69787.html

    上一篇: What are С++ temporaries?

    下一篇: Are there any Maven based HTML Validators