1

这可能是这个问题的重复,但那里讨论的示例处理的是内置类型 ( int)。在我目前的工作中,我不断在代码审查中被标记为未能声明这样的函数:

std::string getName();

作为:

const std::string getName();

后者对我来说似乎毫无意义,但我的一些同事认为const限定符可以让编译器有机会避免复制返回值(或一些类似的优化)。我gcc -s在这个小测试程序上运行:

#include <iostream>
#include <string>

const std::string name()
{
    return "World";
}

int main()
{
    std::cout << "Hello, " << name() << '!' << std::endl;
    return 0;
}

const如果我从name()函数中删除,生成的程序集是相同的。但是,我的同事建议,const限定符提供的优化(如果有的话)可能是特定于平台/编译器的,所以最好加上const“以防万一”。我不愿采用这种习惯,因为我还没有设法在野外找到任何这样做的代码。

所以我的问题是两方面的:

  1. 上面例子中的const限定符有什么不同吗?
  2. 如果返回值是 someclass Foo而不是 的实例,这有关系std::string吗?

我的同事们都是相当通情达理的人,并且对这样的想法持开放态度,即这是一个古怪的约定,或多或少偶然地潜入了我们的代码库。但是没有人对这种特殊用法有足够的把握,以至于他们愿意说这完全没有必要。

4

2 回答 2

2

在您的示例中,我没有看到任何 (N)RVO。[class.copy]/31 中允许但不强制要求RVO 或复制/移动省略。这里有些例子:

#include <iostream>

struct A
{
    A(int i) { std::cout << "ctor\n"; }
    ~A() { std::cout << "dtor\n"; }
    A& operator=(A const&)
    { std::cout << "copy-assignment-op\n"; return *this; }

    // N.B. no default move ctor will be created!
};

A foo() { return 42; }
A bar() { return A(42); }

const A cfoo() { return 42; }
const A cbar() { return A(42); }

int main()
{
    std::cout << "A a(42);\n";
    A a(42);
    std::cout << "\na = foo();\n";
    a = foo();
    std::cout << "\na = bar();\n";
    a = bar();

    std::cout << "\nA b( foo() );\n";
    A b( foo() );
    std::cout << "\nA c( bar() );\n";
    A c( bar() );

    std::cout << "\nA d( cfoo() );\n";
    A d( cfoo() );
    std::cout << "\nA e( cbar() );\n";
    A e( cbar() );

    std::cout << "\ndtors following for a, b, c, d, e\n";
}

最新编译器的输出(许多甚至 at -O0)是:

一个(42);
演员

a = foo();
演员
复制分配操作
dtor

一个 = 酒吧();
演员
复制分配操作
dtor

一个 b( foo() );
演员

一个 c(bar());
演员

一个 d( cfoo() );
演员

一个 e( cbar() );
演员

dtors 跟随 a, b, c, d, e
dtor
dtor
dtor
dtor
dtor

如您所见,返回类型是否const不影响 RVO。但是,它可以,因为它不是强制性的。因此,如果您有一个旧的/奇怪的编译器 - 测试它(或在文档中查找)。


上例中有两种 RVO:

  1. 将具有自动存储持续时间的对象复制/移动到返回值(也称为 NRVO),例如

    A foo() { A a; return a; }
    

    [class.copy]/31 允许这个

    return具有类返回类型的函数的语句中,当表达式是具有与函数返回类型相同的 cv 非限定类型的非易失性自动对象(函数或 catch 子句参数除外)的名称时

  2. 将临时的省略复制/移动到另一个对象

    A a( foo() ); // only 1 ctor is called
    A foo() { return A(); } // no copy/move from the temporary to the return value
    

    这是允许的

    当尚未绑定到引用 (12.2) 的临时类对象将被复制/移动到具有相同 cv 非限定类型的类对象时

const其中“cv-unqualified”(可能)意味着这种优化忽略了顶级合格。

于 2013-10-12T20:26:37.913 回答
1

在这两种情况下

std::string getName();

const std::string getName();

会有一个字符串的副本。但是,只要你写(类似):

std::string name;
...
name = getName();

如果getName()的返回类型是 const 或不是 const,这将完全没有区别,编译器将优化副本并将内部值getName直接复制到name变量中,而不进行临时副本。

但是,如果您改为返回引用:

std::string& getName();

或者

const std::string& getName();

它有很大的不同,因为第一种形式可以让你这样做:

getName() = "Mats";

这可能不是您希望界面用户执行的操作。

对我来说,评论者似乎混淆了这两个变体。

于 2013-10-12T20:09:44.373 回答