在C ++中禁用复制省略

Vla*_*ost 7 c++ copy-constructor

免责声明:研究目标是如何为提供的代码部分禁用复制省略和返回值优化。如果要提及诸如XY问题之类的内容,请避免回答。问题具有严格的技术和研究性质,并以此方式强有力地提出

在C ++ 14中,引入了复制省略和返回值优化。如果某个对象已在一个表达式中被销毁并进行了复制构造,例如复制分配或从函数按值返回立即值,则将删除复制构造器。

以下推理应用于复制构造函数,但是可以对move构造函数执行类似的推理,因此不再赘述。

有一些针对自定义代码禁用复制省略的部分解决方案:

1) Compiler-dependent option. For GCC, there is solution based on __attribule__ or #pragma GCC constructions, like this /sf/answers/2343277541/ . But since it compiler-dependent, it does not met question.

2) Force-disabling copy-constructor, like Clazz(const Clazz&) = delete. Or declare copy-constructor as explicit to prevent it's using. Such solution does not met task since it changes copy-semantics and forces introducing custom-name functions like Class::copy(const Clazz&).

3) Using intermediate type, like describe here /sf/answers/1136663741/ . Since this solution forces to introduce new descendant type, it does not met question.

After some research there was found that reviving temporary value can solve question. If reinterpret source class as reference to one-element array with this class and extract first element, then copy elision will turned off. Template function can be written like this:

template<typename T, typename ... Args> T noelide(Args ... args) {
    return (((T(&)[1])(T(args...)))[0]);
}
Run Code Online (Sandbox Code Playgroud)

Such solution works good in most cases. In following code it generates three copy-constructor invocations - one for direct copy-assignment and two for assignment with return from function. It works good in MSVC 2017

#include <iostream>

class Clazz {
public: int q;
    Clazz(int q) : q(q) { std::cout << "Default constructor " << q << std::endl; }
    Clazz(const Clazz& cl) : q(cl.q) { std::cout << "Copy constructor " << q << std::endl; }
    ~Clazz() { std::cout << "Destructor " << q << std::endl; }
};

template<typename T, typename ... Args> T noelide(Args ... args) {
    return (((T(&)[1])(T(args...)))[0]);
}

Clazz func(int q) {
    return noelide<Clazz>(q);
}

int main() {
    Clazz a = noelide<Clazz>(10);
    Clazz b = func(20);
    const Clazz& c = func(30);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

This approach works good for a and b cases, but performs redundant copy with case c - instead of copy, reference to temporary should be returned with lifetime expansion.

Question: how to modify noelide template to allow it work fine with const lvalue-reference with lifetime expansion? Thanks!

Jaa*_*a-c 5

根据 N4140,2031 年 8 月 12 日:

\n\n
\n

...

\n\n

这种复制/移动操作的省略称为复制省略,在以下情况下是允许的(可以组合起来消除多个副本):

\n\n

(31.1) \xe2\x80\x94 在具有类返回类型的函数中的 return 语句中,\n 当表达式是非易失性自动对象的名称时\n(函数或 catch 子句参数除外)与函数返回类型相同\n cv非限定类型,可以通过直接将自动对象构造\n到函数\xe2\x80\x99s返回值中来省略复制/移动\n操作

\n\n

(31.3) \xe2\x80\x94 当尚未绑定到引用 (12.2) 的临时类对象被复制/移动到具有相同 cv 不合格类型的类对象时,复制/移动通过将临时对象直接构建到被省略的复制/移动的目标中,可以省略操作

\n
\n\n

因此,如果我理解正确的话,只有当 return 语句是局部变量的名称时,才会发生复制省略。因此,您可以通过返回例如“禁用”复制省略return std::move(value)...如果您不喜欢move为此使用,您可以简单地实现noelidestatic_cast<T&&>(...).

\n


luk*_*k32 4

鉴于您的所有限制,这是不可能完成的。很简单,因为该标准没有提供关闭 RVO 优化的方法。

您可以通过破坏其中一项要求来阻止强制应用 RVO,但无法可靠地阻止可选的允许优化。此时,您所做的一切要么是更改语义,要么是特定于编译器(例如-fno-elide-constructorsGCC 和 Clang 的选项)。