Search code examples
c++c++11stdsmart-pointerspass-by-value

Is there a point to define move-only objects in c++11?


I had a question about using unique-ptrs before. I get this answer recommending to use move-only objects. I defined a class as below:

class B {
    const string objName;
public:

    B ( B && ) = default;
    B &  operator= ( B && ) = default;
    B ( const B & ) = delete;
    B & operator= ( const B & ) = delete;

    B(const string & name) :
            objName(name) {

    }

    virtual ~B();

    const string name() const { return objName;};
}

and I called B by this lines:

class A {
A(){}
void take(B b);
}

A a; 
B b("test");
cout<<b.name();
a.take(std::move(b));
cout<<b.name();

My questions:

  1. Even if I have defaulted the move constructor, I can not write a.take(b) and I am getting compiling error. I understand that copy constrctor is deleted but It seems that the logical choice is to use move constructor when it is defaulted without need for writing std::move like this a.take(b)
  2. In the result the "test" is printed twice. Why the b object has not been removed after calling move? If the b object still exists and a copy of it has been sent to the a.take(move(b)) then it means we don't have any use of move for rvalue objects.
  3. Is it a good practice to use move-only objects as above (Removing copy constructor and assignment operator and defaulting move constructor and move assignment)?

Solution

  • Yes, there is a point. Objects which manage resources (perhaps physical ones) that cannot/should not be shared between objects is the first example that comes to mind.

    1) You wrote it incorrectly. Here is what I think you want based on this question and your previous one.

    class B {
        std::string objName;
    public:
        B ( B && ) = default;
        B &  operator= ( B && ) = default;
        B ( const B & ) = delete;
        B & operator= ( const B & ) = delete;
        B(const std::string & name) :
                objName(name) {}
        virtual ~B() {}
        std::string name() const { return objName;}
    };
    
    class A {
    public:
       std::vector<B> v;
       void take(B && b)
       {
          v.push_back(std::move(b));
       }
    };
    
    int main()
    {
       A a;
       B b("test");
    
       std::cout << "Before: " << b.name() << std::endl;
       a.take(std::move(b));
       std::cout << "After: " << b.name() << std::endl;
    
       std::cout << "A has elements: " << std::endl;
       for(B &b : a.v)
          std::cout << "  " << b.name() << std::endl;
    }
    

    2) You're accessing a value that has been moved-from, which doesn't make any sense! This answer already does a good job of explaining, but below I've also included text from a STL reference for std::move.

    http://en.cppreference.com/w/cpp/utility/move

    Unless otherwise specified, all standard library objects that have been moved from are placed in a valid but unspecified state. That is, only the functions without preconditions, such as the assignment operator, can be safely used on the object after it was moved from.

    3) I have found two legitimate uses in my experience. In both cases the move-only objects controlled a physical resource which if shared by two objects would break everything.