在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();
答案 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)
这确实是一个构造函数调用,一个表达式,用于评估X
类型的临时对象。 X([...])
形式的表达式X
是类型的名称是构造函数调用,它们创建X
类型的临时对象(虽然我不知道如何在适当的标准中解释它,并且有一些特殊情况,解析器可以表现不同)。这与您在f5
和f6
函数中使用的结构相同,只是省略了可选的ii
参数。
由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
本身不再起作用)。
答案 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();
}