Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
108 views
in Technique[技术] by (71.8m points)

c++ - What constitutes a valid state for a "moved from" object in C++11?

I've been trying to wrap my head around how move semantics in C++11 are supposed to work, and I'm having a good deal of trouble understanding what conditions a moved-from object needs to satisfy. Looking at the answer here doesn't really resolve my question, because can't see how to apply it to pimpl objects in a sensible way, despite arguments that move semantics are perfect for pimpls.

The easiest illustration of my problem involves the pimpl idiom, like so:

class Foo {
    std::unique_ptr<FooImpl> impl_;
public:
    // Inlining FooImpl's constructors for brevity's sake; otherwise it 
    // defeats the point.
    Foo() : impl_(new FooImpl()) {}

    Foo(const Foo & rhs) : impl_(new FooImpl(*rhs.impl_)) {}

    Foo(Foo && rhs) : impl_(std::move(rhs.impl_)) {}

    Foo & operator=(Foo rhs) 
    {
        std::swap(impl_, rhs.impl_);

        return *this;
    }

    void do_stuff () 
    {
        impl_->do_stuff;
    }
};

Now, what can I do once I've moved from a Foo? I can destroy the moved-from object safely, and I can assign to it, both of which are absolutely crucial. However, if I try to do_stuff with my Foo, it will explode. Before I added move semantics for my definition of Foo, every Foo satisfied the invariant that it could do_stuff, and that's no longer the case. There don't seem to be many great alternatives, either, since (for example) putting the moved-from Foo would involve a new dynamic allocation, which partially defeats the purpose of move semantics. I could check whether impl_ in do_stuff and initialize it to a default FooImpl if it is, but that adds a (usually spurious) check, and if I have a lot of methods it would mean remembering to do the check in every one.

Should I just give up on the idea that being able to do_stuff is a reasonable invariant?

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You define and document for your types what a 'valid' state is and what operation can be performed on moved-from objects of your types.

Moving an object of a standard library type puts the object into an unspecified state, which can be queried as normal to determine valid operations.

17.6.5.15 Moved-from state of library types                                         [lib.types.movedfrom]

Objects of types defined in the C++ standard library may be moved from (12.8). Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.

The object being in a 'valid' state means that all the requirements the standard specifies for the type still hold true. That means you can use any operation on a moved-from, standard library type for which the preconditions hold true.

Normally the state of an object is known so you don't have to check if it meets the preconditions for each operation you want to perform. The only difference with moved-from objects is that you don't know the state, so you do have to check. For example, you should not pop_back() on a moved-from string until you have queried the state of the string to determine that the preconditions of pop_back() are met.

std::string s = "foo";
std::string t(std::move(s));
if (!s.empty()) // empty has no preconditions, so it's safe to call on moved-from objects
    s.pop_back(); // after verifying that the preconditions are met, pop_back is safe to call on moved-from objects

The state is probably unspecified because it would be onerous to create a single useful set of requirements for all different implementations of the standard library.


Since you are responsible not only for the specification but also the implementation of your types, you can simply specify the state and obviate the need for querying. For example it would be perfectly reasonable to specify that moving from your pimpl type object causes do_stuff to become an invalid operation with undefined behavior (via dereferencing a null pointer). The language is designed such that moving only occurs either when it's not possible to do anything to the moved-from object, or when the user has very obviously and very explicitly indicated a move operation, so a user should never be surprised by a moved-from object.


Also note that the 'concepts' defined by the standard library do not make any allowances for moved-from objects. That means that in order to meet the requirements for any of the concepts defined by the standard library, moved-from objects of your types must still fulfill the concept requirements. This means that if objects of your type don't remain in a valid state (as defined by the relevant concept) then you cannot use it with the standard library (or the result is undefined behavior).


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...