可以通过引用返回临时对象

时间:2009-11-11 12:26:08

标签: c++

是否可以从此示例代码中的函数返回引用:

string &erase_whitespace(string &text)
{
    text.erase(**etc.**);
    return text;
}

呼叫:

string text = erase_whitespace(string("this is a test"));
cout << test;

此代码有效吗?在Visual C ++上,它不会崩溃,但看起来不对。

由于

10 个答案:

答案 0 :(得分:9)

来自C ++ 2003标准(草案)的第12.2.3节

  

临时对象在评估全表达式(1.9)的最后一步时被销毁,该表达式(词法上)包含创建它们的点。

§12.2.4:

  

有两种情况下,临时人员在不同时刻被摧毁,而不是在完全结束时   表达。 ...

§12.2.5:

  

第二个上下文是引用绑定到临时的。引用所在的临时值   绑定或临时绑定临时绑定的子对象的完整对象仍然存在   除了下面指定的以外,参考的有效期。 ...临时绑定到引用   函数调用(5.2.2)中的参数一直持续到包含调用的完整表达式完成为止。

§8.5.3.5决定何时引用必须是const类型。如果临时是具有转换运算符的类的实例,则临时绑定到非const引用是可能的,该转换运算符返回适当的引用(这是一个满口)。一个例子可能更容易理解:

class Foo {
    ...
    operator Bar&() const;
...
void baz(Bar &b);
...
    baz(Foo()); // valid
    baz(Bar()); // not valid

由于§12.3.2.1,最后一行无效,其中规定“转换函数永远不会用于将[an ...]对象转换为[...]相同的对象类型(或引用 它可以通过Bar的祖先和虚拟转换函数使用转换来使它工作。

赋值是一个表达式(第5.17节),因此代码中的完整表达式(第1.9.12节)就是赋值。这给出了以下序列(暂时忘记了临时字符串可能无法绑定到非const引用):

  1. 创建临时字符串
  2. 临时绑定到string& text
  3. erase_whitespace参数
  4. erase_whitespace做到了。
  5. erase_whitespace返回对临时
  6. 的引用
  7. 临时复制到string text
  8. 临时被毁。
  9. 所以在这种情况下,所有人都是犹太人。正如Mike Seymour指出的那样,问题是将erase_whitespace的结果分配给引用。请注意,这可能不会立即引起问题,因为存储字符串的区域可能包含与临时销毁之前相同的数据。下次在堆栈或堆上分配某些内容时,...

答案 1 :(得分:7)

如果您使用的是Visual C,那么最好执行以下操作:

string erase_whitespace (const string &input)
{
  string output = input.erase (...);
  return output;
}

它可能看起来更糟,但编译器在构建优化版本时可以使用Named Return Value Optimisations,这消除了按值返回的开销(即消除了按值返回所涉及的复制构造函数)。因此,它不仅看起来正确,而且可能更有效率。

答案 2 :(得分:4)

这里有两个问题。

首先,当然允许返回引用,例如:

struct myclass
{
    myclass& foo()
    { cout << "myclass::foo()"; return *this }
    myclass& bar()
    { cout << "myclass::bar()"; return *this }
};
...
myclass obj;
obj.foo().bar();

其次,在C ++中不允许将临时对象传递给非const引用(在SO上讨论了很多,只是搜索它):

// passing string("this is a test") is wrong
string text = erase_whitespace(string("this is a test"));

不幸的是,一些编译器(例如VC)允许这种行为,尽管它不是标准的。如果你转动编译器警告级别,至少应该收到警告。

答案 3 :(得分:3)

它看起来不对, 错误,因为不允许非const引用绑定到rvalues。在您的情况下,编译器似乎接受它作为扩展。你不应该依赖它。

至于可能的chrashes:启用此编译器扩展时,这没有问题。但这不会:

string const& dangling_reference =
    erase_whitespace(string("this is a test"));

因为该函数返回对将被销毁的临时对象的引用。如果按值返回,由于特殊的C ++规则,该行将是安全的(临时的生命期将被延长)。

另一个缺点是该函数正在改变其参数。这可能是一个带有字符串并返回字符串的函数的意外行为。

如果您以这种方式编写功能以提高性能,您可以尝试这个并测量它:

string erase_whitespace(string text)
{
    text.erase(**etc.**);
    string ret; ret.swap(text);
    return ret;
}

如果你有一个好的编译器可以忽略不必要的副本,这应该表现得很好。具体而言,如果使用rvalue调用函数,则编译器可以忽略副本以按值接受参数。您的编译器也可能足够智能以应用NRVO(命名返回值优化)。如果是那么聪明,请使用以下代码

string foo = erase_whithespace("  blah  ");

不会调用std :: string的任何副本ctor。交换只是因为当返回参数时,当前没有编译器能够应用NRVO。

答案 4 :(得分:2)

我不确定你的例子有多么具有代表性,但你的函数看起来很好,只要你记录它修改了传入的对象。这是虚假的召唤。我不明白为什么你不能写:

std::string text("this is a test");
erase_whitespace(text);
cout << test;

与您希望在原始代码中发生的事情完全相同,即创建std::string,删除其空白并输出它,同时保持{{1将对象本身放在堆栈而不是堆中,并最小化副本。当然,std::string的实际存储仍然存在于堆中,但是当函数或块完成时,您可以获得C ++处理该东西的好处。

我不明白为什么当它转换为没有实际编译代码时,你需要保存这行代码。

现在,如果你真的需要这个,比如说,填入方程式,通常要做的事情就像Skizz上面写的那样(在常规C ++中工作,而不仅仅是Visual C):

std::string

有什么好处是你可以传递一个std::string erase_whitespace (const string &input) { std::string output(input); output.erase (...); return output; } 作为参数,C ++会自动从中创建一个临时const char*,因为std::string有一个带{的构造函数{1}}。此外,这不会影响传入的对象。正如Skizz所指出的那样,优化者喜欢这种构造返回值的方法。

答案 5 :(得分:0)

更好的方法是明确地将其作为堆分配w / pointer:

string* erase_whitespace(string* text)
{
    text->erase(**etc.**);
    return text;
}

string* text = erase_whitespace(new string("this is a test"));
cout << *text;
delete text;

答案 6 :(得分:0)

您的返回参数以及函数参数需要const-reference。在这方面,C ++是不兼容的编译器,允许临时作为非const引用传递。

答案 7 :(得分:0)

雅肯定它应该有用..

因为string text = erase_whitespace(string(“this is a test”))

由编译器转换为以下代码:

string temp(“这是一个测试”);

string text = erase_whitespace(temp);

答案 8 :(得分:0)

我同意迈克的观点,但我还要提到,我认为首先传递对string的引用并不是一个好主意。我很确定字符串类是轻量级传递值,因为它通过内部引用存储实际的字符数组。

答案 9 :(得分:0)

在这种情况下,你给它做的工作。当您尝试使用return作为参考时,会出现问题。即:

string& text = erase_whitespace(string("this is a test"));

这是完全有效的VC ++代码,但您现在已进入“未定义”领域。您的主要问题来自这样一个事实,即使用此代码的任何其他人都不会知道他们无法执行此操作。

所有这一切都是非常危险的代码,只适用于VC ++。

Skizz的回复为您提供的代码可以完美地作为替代品,并解决上述所有问题。

相关问题