本文介绍了从工厂功能就地初始化不可复制的成员(或其他对象)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

一个类必须具有有效的副本或移动构造函数,此语法中的任何一种才合法:

A class must have a valid copy or move constructor for any of this syntax to be legal:

C x = factory();
C y( factory() );
C z{ factory() };

在C ++ 03中,很常见的情况是依靠复制省略来防止编译器接触到复制构造函数。每个类都有一个有效的副本构造函数 signature ,无论是否存在定义。

In C++03 it was fairly common to rely on copy elision to prevent the compiler from touching the copy constructor. Every class has a valid copy constructor signature regardless of whether a definition exists.

在C ++ 11中,不可复制类型应定义 C(C const&)= delete; ,使对该函数的任何引用均无效,无论其使用如何(与不可移动相同)。 (C ++ 11§8.4.3/ 2)。例如,GCC在尝试按值返回这样的对象时会抱怨。复制省略不再提供帮助。

In C++11 a non-copyable type should define C( C const & ) = delete;, rendering any reference to the function invalid regardless of use (same for non-moveable). (C++11 §8.4.3/2). GCC, for one, will complain when trying to return such an object by value. Copy elision ceases to help.

幸运的是,我们还有新的语法来表达意图,而不是依靠漏洞。 factory 函数可以返回 braised-init-list 来临时构造结果:

Fortunately, we also have new syntax to express intent instead of relying on a loophole. The factory function can return a braced-init-list to construct the result temporary in-place:

C factory() {
    return { arg1, 2, "arg3" }; // calls C::C( whatever ), no copy
}






编辑:如有疑问,该 return 语句的解析如下:


If there's any doubt, this return statement is parsed as follows:


  1. 6.6.3 / 2:带有括号初始化列表的return语句通过复制列表初始化来初始化要从函数返回的对象或引用( 8.5.4)从指定的初始化程序列表中进行。

  2. 8.5.4 / 1:在复制初始化上下文中的列表初始化称为复制列表初始化。 ¶3:如果T是类类型,则考虑构造函数。枚举适用的构造函数,并通过重载分辨率(13.3、13.3.1.7)选择最佳的构造函数。

不要被名称​​ copy-list-initialization 所误导。 8.5:

Do not be misled by the name copy-list-initialization. 8.5:

14:发生的初始化形式为
T x = a;
以及参数传递,函数返回,抛出异常(15.1),处理异常( 15.3),并且聚合成员初始化(8.5.1)称为复制初始化。

14: The initialization that occurs in the form T x = a; as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization.

两个副本-initialization及其替代方法 direct-initialization 在初始化程序为大括号init-list时始终遵循列表初始化。添加 = 没有语义影响,这是列表初始化被非正式地称为统一初始化的原因之一。

Both copy-initialization and its alternative, direct-initialization, always defer to list-initialization when the initializer is a braced-init-list. There is no semantic effect in adding the =, which is one reason list-initialization is informally called uniform initialization.

有所不同:直接初始化可能会调用显式构造函数,这与复制初始化不同。复制初始化会初始化一个临时文件,并在转换时将其复制以初始化对象。

There are differences: direct-initialization may invoke an explicit constructor, unlike copy-initialization. Copy-initialization initializes a temporary and copies it to initialize the object, when converting.

复制列表初始化的规范为 return {list} 语句仅将确切的等效语法指定为 temp T = {list}; ,其中 = 表示复制初始化。这并不意味着立即调用了副本构造函数。

The specification of copy-list-initialization for return { list } statements merely specifies the exact equivalent syntax to be temp T = { list };, where = denotes copy-initialization. It does not immediately imply that a copy constructor is invoked.

-结束编辑。

然后可以将函数结果接收到右值引用中,以防止将临时副本复制到本地:

The function result can then be received into an rvalue reference to prevent copying the temporary to a local:

C && x = factory(); // applies to other initialization syntax






问题是,如何从返回不可复制,不可移动类型的工厂函数初始化非静态成员?参考技巧不起作用,因为参考成员不会延长临时人员的寿命。


The question is, how to initialize a nonstatic member from a factory function returning non-copyable, non-moveable type? The reference trick doesn't work because a reference member doesn't extend the lifetime of a temporary.

注意,我不考虑聚合初始化。

Note, I'm not considering aggregate-initialization. This is about defining a constructor.

推荐答案

关于您的主要问题:

问题是,如何从返回非可复制,不可移动类型的工厂函数初始化非静态成员?

您不知道。

您的问题是您试图混淆两件事:返回值的生成方式和返回值的生成方式在呼叫站点上已使用。这两件事没有相互联系。请记住:函数的定义不会影响使用方式(就语言而言),因为编译器不一定可以使用该定义。因此,C ++不允许使用返回值的生成方式来影响任何东西(省略之外,这是一种优化,而不是语言要求)。

Your problem is that you are trying to conflate two things: how the return value is generated and how the return value is used at the call site. These two things don't connect to each other. Remember: the definition of a function cannot affect how it is used (in terms of language), since that definition is not necessarily available to the compiler. Therefore, C++ does not allow the way the return value was generated to affect anything (outside of elision, which is an optimization, not a language requirement).

换句话说:

C c = {...};

与此不同:

C c = [&]() -> C {return {...};}()

您有一个返回类型的函数值。它返回类型为 C 的prvalue表达式。如果要存储此值并为其命名,则有两种选择:

You have a function which returns a type by value. It is returning a prvalue expression of type C. If you want to store this value, thus giving it a name, you have exactly two options:


  1. 将其存储为 const& & 。这会将临时设备的寿命延长到控制块的寿命。您不能使用成员变量来做到这一点;

  1. Store it as a const& or &&. This will extend the lifetime of the temporary to the lifetime of the control block. You can't do that with member variables; it can only be done with automatic variables in functions.

将其复制/移动到一个值中。您可以使用成员变量来执行此操作,但是显然它要求类型是可复制或可移动的。

Copy/move it into a value. You can do this with a member variable, but it obviously requires the type to be copyable or moveable.

这些是如果要存储prvalue表达式,则C ++唯一可用的选项。因此,您可以使该类型可移动,也可以将一个刚分配的指针返回到内存并存储它而不是值。

These are the only options C++ makes available to you if you want to store a prvalue expression. So you can either make the type moveable or return a freshly allocated pointer to memory and store that instead of a value.

此限制是移动原因的很大一部分最初是为了创造:能够按价值传递事物并避免昂贵的复制品。无法更改语言以强制省略返回值。因此,他们在许多情况下都降低了成本。

This limitation is a big part of the reason why moving was created in the first place: to be able to pass things by value and avoid expensive copies. The language couldn't be changed to force elision of return values. So instead, they reduced the cost in many cases.

这篇关于从工厂功能就地初始化不可复制的成员(或其他对象)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-01 10:29