简体   繁体   English

具有移动语义的 RAII 类中的默认构造函数应该做什么?

[英]What should the default constructor do in a RAII class with move semantics?

Move semantics are great for RAII classes.移动语义非常适合 RAII 类。 They allow one to program as if one had value semantics without the cost of heavy copies.它们允许人们像具有值语义一样进行编程,而无需大量复制。 A great example of this is returning std::vector from a function .一个很好的例子是从函数返回 std::vector Programming with value semantics however means, that one would expect types to behave like primitive data types.然而,使用值语义编程意味着,人们希望类型的行为类似于原始数据类型。 Those two aspects sometimes seem to be at odds.这两个方面有时似乎是矛盾的。

On the one hand, in RAII one would expect the default constructor to return a fully initialized object or throw an exception if the resource acquisition failed.一方面,在 RAII 中,如果资源获取失败,人们会期望默认构造函数返回一个完全初始化的对象或抛出异常。 This guarantees that any constructed object will be in a valid and consistent state (ie safe to use).这保证了任何构造的对象都将处于有效且一致的状态(即可以安全使用)。

On the other hand, with move semantics there exists a point when objects are in a valid but unspecified state .另一方面,对于移动语义,当对象处于有效但未指定的状态时存在一个点。 Similarly, primitive data types can be in an uninitialized state.类似地,原始数据类型可以处于未初始化状态。 Therefore, with value semantics, I would expect the default constructor to create an object in this valid but unspecified state, so that the following code would have the expected behavior:因此,对于值语义,我希望默认构造函数在此有效但未指定的状态下创建一个对象,以便以下代码具有预期的行为:

// Primitive Data Type, Value Semantics
int i;
i = 5;

// RAII Class, Move Semantics
Resource r;
r = Resource{/*...*/}

In both cases, I would expect the "heavy" initialization to occur only once.在这两种情况下,我都希望“重”初始化只发生一次。 I am wondering, what is the best practice regarding this?我想知道,关于此的最佳做法是什么? Obviously, there is a slight practical issue with the second approach: If the default constructor creates objects in the unspecified state, how would one write a constructor that does acquire a resource, but takes no additional parameters?显然,第二种方法有一个轻微的实际问题:如果默认构造函数创建处于未指定状态的对象,那么如何编写一个获取资源但不接受额外参数的构造函数? (Tag dispatching comes to mind...) (想到了标签调度......)

Edit: Some of the answers have questioned the rationale of trying to make your classes work like primitive data types.编辑:一些答案质疑试图让你的类像原始数据类型一样工作的理由。 Some of my motivation comes from Alexander Stepanov's Efficient Programming with Components , where he talks about regular types.我的一些动机来自Alexander Stepanov 的 Efficient Programming with Components ,他在那里谈到了常规类型。 In particular, let me quote:特别是,让我引用:

Whatever is a natural idiomatic expression in c [for built-in types], should be a natural idiomatic expression for regular types.无论是 c [内置类型] 中的自然惯用表达式,都应该是正则类型的自然惯用表达式。

He goes on to provide almost the same example as above.他接着提供了与上面几乎相同的例子。 Is his point not valid in this context?他的观点在这种情况下无效吗? Am I understanding it wrong?我理解错了吗?

Edit: As there hasn't been much discussion, I am about to accept the highest voted answer.编辑:由于没有太多讨论,我即将接受最高投票的答案。 Initializing objects in a "moved-from like" state in the default constructor is probably not a good idea, since everyone who agreed with the existing answers would not expect that behavior.在默认构造函数中以“moved-from like”状态初始化对象可能不是一个好主意,因为同意现有答案的每个人都不会期望这种行为。

Programming with value semantics however means, that one would expect types to behave like primitive data types.然而,使用值语义编程意味着,人们希望类型的行为类似于原始数据类型。

Keyword "like".关键字“喜欢”。 Not "identically to".不是“完全相同”。

Therefore, with value semantics, I would expect the default constructor to create an object in this valid but unspecified state因此,对于值语义,我希望默认构造函数在这种有效但未指定的状态下创建一个对象

I really don't see why you should expect that.我真的不明白为什么你应该期待那个。 It doesn't seem like a very desirable feature to me.这对我来说似乎不是一个非常理想的功能。

what is the best practice regarding this?这方面的最佳做法是什么?

Forget this idea that a non POD class should share this feature in common with primitive data types.忘记非 POD 类应该与原始数据类型共享此功能的想法。 It's wrong headed.走错方向了If there is no sensible way to initialize a class without parameters, then that class should not have a default constructor.如果没有明智的方法来初始化一个没有参数的类,那么该类不应该有一个默认的构造函数。

If you want to declare an object, but hold off on initializing it (perhaps in a deeper scope), then use std::unique_ptr .如果你想声明一个对象,但推迟初始化它(可能在更深的范围内),那么使用std::unique_ptr

If you accept that objects should generally be valid by construction, and all possible operations on an object should move it only between valid states, then it seems to me that by having a default constructor, you are only saying one of two things:如果您接受对象通常应通过构造有效,并且对象上的所有可能操作都应仅在有效状态之间移动它,那么在我看来,通过使用默认构造函数,您只是在说两件事之一:

  • This value is a container, or another object with a reasonable “empty” state, which I intend to mutate—eg, std::vector .这个值是一个容器,或者另一个具有合理“空”状态的对象,我打算改变它——例如, std::vector

  • This value does not have any member variables, and is used primarily for its type—eg, std::less .这个值没有任何成员变量,主要用于它的类型——例如, std::less

It doesn't follow that a moved-from object need necessarily have the same state as a default-constructed one.这并不意味着移动后,从对象需要一定会有相同的状态作为缺省构造之一。 For example, an std::string containing the empty string "" might have a different state than a moved-from string instance.例如,包含空字符串""std::string可能与移出的string实例具有不同的状态。 When you default-construct an object, you expect to work with it;当您默认构造一个对象时,您希望使用它; when you move from an object, the vast majority of the time you simply destroy it.当你从一个物体上移动时,绝大多数情况下你只是简单地摧毁它。

How would one write a constructor that does acquire a resource, but takes no additional parameters?如何编写一个获取资源但不接受额外参数的构造函数?

If your default constructor is expensive and takes no parameters, I would question why.如果您的默认构造函数很昂贵并且不带参数,我会质疑为什么。 Should it really be doing something so expensive?它真的应该做这么昂贵的事情吗? Where are its default parameters coming from—some global configuration?它的默认参数来自哪里——一些全局配置? Maybe passing them explicitly would be easier to maintain.也许明确地传递它们会更容易维护。 Take the example of std::ifstream : with a parameter, its constructor opens a file;std::ifstream为例:带一个参数,它的构造函数打开一个文件; without, you use the open() member function.没有,您使用open()成员函数。

What you can do is lazy initialization: have a flag (or a nulled pointer) in your object that indicates whether the object is fully initialized.您可以做的是延迟初始化:在您的对象中有一个标志(或一个空指针),指示该对象是否已完全初始化。 Then have a member function that uses this flag to ensure initialization after it is run.然后有一个成员函数,它使用这个标志来确保运行后的初始化。 All your default constructor needs to do is to set the initialization flag to false.您的默认构造函数需要做的就是将初始化标志设置为 false。 If all members that need an initialized state call ensure_initialization() before starting their work, you have perfect semantics and no double heavy initialization.如果所有需要初始化状态的成员在开始工作之前调用ensure_initialization() ,则您将拥有完美的语义并且没有双重繁重的初始化。

Example:例子:

class Foo {
public:
    Foo() : isInitialized(false) { };

    void ensureInitialization() {
        if(isInitialized) return;
        //the usual default constructor code
        isInitialized = true;
    };

    void bar() {
        ensureInitialization();
        //the rest of the bar() implementation
    };

private:
    bool isInitialized;
    //some heavy variables
}

Edit: To reduce the overhead produced by the function call, you can do something like this:编辑:要减少函数调用产生的开销,您可以执行以下操作:

//In the .h file:
class Foo {
public:
    Foo() : isInitialized(false) { };
    void bar();

private:
    void initialize();

    bool isInitialized;
    //some heavy variables
}

//In the .cpp file:
#define ENSURE_INITIALIZATION() do { \
    if(!isInitialized) initialize(); \
} while(0)

void Foo::bar() {
    ENSURE_INITIALIZATION();
    //the rest of the bar() implementation
}

void Foo::initialize() {
    //the usual default constructor code
    isInitialized = true;
}

This makes sure that the decision to initialize or not is inlined without inlining the initialization itself.这确保了初始化或不初始化的决定是内联的,而不内联初始化本身。 The later would just bloat the executable and reduce instruction cache efficiency, but the first can't be done automatically, so you need to employ the preprocessor for that.后者只会使可执行文件膨胀并降低指令缓存效率,但第一个不能自动完成,因此您需要为此使用预处理器。 The overhead of this approach should be less than a function call on average.这种方法的开销平均应该小于函数调用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM