复制省略是否在默认函数参数中有效?

时间:2016-02-11 10:34:51

标签: c++ language-lawyer

考虑以下代码:

struct foo;
foo *p;   

struct foo {
    foo() { p = this; }
};

bool default_arg ( foo f = foo() )
{
    return p == &f;
}

bool passed_in ( foo& f )
{
    return p == &f;
}   

int main()
{
    std::cout << default_arg() << '\n';

    foo f = foo();
    std::cout << passed_in(f) << '\n';
}

我希望对default_argpassed_in的调用,f只是默认构造,因为副本将被省略*。这会导致两次调用都返回true。但是,Clang 3.7 nor GCC 5.3default_arg默认参数中的{{3}}都不会删除副本。

复制省略是否在默认参数中有效?也许我遗漏了一些明显的问题,必须在每次调用时评估默认参数。

编辑:重要的部分似乎是存在用户声明的复制构造函数。如果存在,则发生复制省略。为什么这会产生影响?

*显然复制elision目前是可选的,但我希望Clang和GCC尽可能这样做。

3 个答案:

答案 0 :(得分:3)

正如T.C.指出的那样,当将小的可复制类型传递给函数时,可能无法进行复制省略。

这是因为某些ABI(例如System V ABI)的调用约定假设足够小的可复制类型将通过寄存器而不是通过内存传递。例如,SysV将在var attr = NSDictionary(object: UIFont(name: "yourFontName", size: 12.0)!, forKey: NSFontAttributeName) self.statusSegmentControl.setTitleTextAttributes(attr, forState: .Normal) 参数类中对这些类型进行分类,而不是INTEGER类。

这意味着如果将这样的参数传递给需要获取参数地址的函数,则需要将寄存器内容复制到堆栈中,以便存在有效地址。因此,无法执行从rvalue参数到by-value参数的复制,即使语言规则可能会说它可能。

当然,在这种情况下,复制省略对于效率来说是无用的,但对于那些好奇的人来说,在这样的平台上进行复制省略的一种简单方法就是使这个类不能轻易复制。一个例子是使用户提供的析构函数:

MEMORY

这会导致在Clang 3.7和GCC 5.3上发生复制。

Live Demo

答案 1 :(得分:0)

不幸的是,我还没有在草稿中找到声明,但在cppreference.com上找到了声明:

  

即使发生了复制省略并且没有调用copy- / move-构造函数,它也必须存在且可访问(好像根本没有进行优化),否则程序就会格式不正确。

我认为这里的问题是,默认的复制/移动构造函数已被优化掉,因此编译器无法忽略副本。但如果你实施其中一个,就会发生以下问题:

#include <iostream>

struct foo;
foo *p;   

struct foo {
    foo() { p = this; std::cout << "default ctor\n"; }

    // define your own copy/move ctor
    // which are doing nothing
    foo(foo const& f) { std::cout << "copy ctor\n"; }
    foo(foo&& f) { std::cout << "move ctor\n"; }

};

bool default_arg ( foo f = foo() )
{
    return p == &f;
}

bool passed_in ( foo& f )
{
    return p == &f;
}   

int main()
{
    std::cout << default_arg() << '\n';

    foo f = foo();
    std::cout << passed_in(f) << '\n';
}

请参阅here

答案 2 :(得分:0)

C ++ 11的规则可以在标准的第12.8(31)章中找到。 4例允许复制省略。这里应该适用的规则是:

  

当一个临时类对象尚未绑定到引用时   将被复制/移动到具有相同cv-unqualified的类对象   型

但我在注释(1.9(11))中找到了这句话:

  

考虑了评估默认参数所涉及的子表达式   要在调用函数的表达式中创建,而不是   定义默认参数的表达式

因此,当foo()默认构造main时,整个事情应该类似于

bool passed_in_by_value ( foo f )
{
    return p == &f;
}

std::cout << passed_in_by_value(foo()) << '\n';

也返回false。也许编译器在将它分配给参数时不会将该值视为临时值。

不是真正的答案,但可能是一个提示......

相关问题