Can we return objects having a deleted/private copy/move constructor by value from a function?

前端 未结 5 2043
走了就别回头了
走了就别回头了 2020-11-29 08:36

In C++03 it is impossible to return an object of a class having a private non-defined copy constructor by value:

struct A { A(int x) { ... } private: A(A con         


        
相关标签:
5条回答
  • 2020-11-29 08:54

    The restriction has not been lifted. As per the access specifier, there is a note in §12.8/32 that explains:

    two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided.

    As of the deleted copy/move constructors §8.4.3/2 states that

    A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed. [ Note: This includes calling the function implicitly or explicitly and forming a pointer or pointer-to-member to the function. It applies even for references in expressions that are not potentially-evaluated. If a function is overloaded, it is referenced only if the function is selected by overload resolution. — end note ]

    Not sure about this particular case, but my understanding of the quote is that, if after the overload resolution in §12.8/32 the deleted copy/move constructor is selected, even if the operation is elided, that could constitute a reference to the function, and the program would be ill formed.

    0 讨论(0)
  • 2020-11-29 08:55

    I was wondering, was this restriction lifted in C++11?

    How could it be? By returning something by value, you are by definition copying (or moving) it. And while C++ can allow that copy/move to be elided in certain circumstances, it's still copying (or moving) by the specification.

    I remember it could be useful to allow callers of a function use the returned object, but that they are not able to copy the value and store it somewhere.

    Yes. You get rid of the copy constructor/assignment, but allow the value to be moved. std::unique_ptr does this.

    You can return a unique_ptr by value. But in doing so, you are returning an "prvalue": a temporary that is being destroyed. Therefore, if you have a function g as such:

    std::unique_ptr<SomeType> g() {...}
    

    You can do this:

    std::unique_ptr<SomeType> value = g();
    

    But not this:

    std::unique_ptr<SomeType> value1 = g();
    std::unique_ptr<SomeType> value2 = g();
    value1 = value 2;
    

    But this is possible:

    std::unique_ptr<SomeType> value = g();
    value = g();
    

    The second line invokes the move assignment operator on value. It will delete the old pointer and move the new pointer into it, leaving the old value empty.

    In this way, you can ensure that the contents of any unique_ptr is only ever stored in one place. You can't stop them from referencing it in multiple places (via pointers to unique_ptr or whatever), but there will be at most one location in memory where the actual pointer is stored.

    Removing both the copy and move constructors creates an immobile object. Where it is created is where it's values stay, forever. Movement allows you to have unique ownership, but without being immobile.

    0 讨论(0)
  • 2020-11-29 09:09

    You could probably hack together a proxy to do the trick if you really wanted, and have a converting constructor that copies the value stored within the proxy.

    Something along the lines of:

    template<typename T>
    struct ReturnProxy {
        //This could be made private, provided appropriate frienship is granted
        ReturnProxy(T* p_) : p(p_) { }
        ReturnProxy(ReturnProxy&&) = default;
    
    private:
        //don't want these Proxies sticking around...
        ReturnProxy(const ReturnProxy&) = delete;
        void operator =(const ReturnProxy&) = delete;
        void operator =(ReturnProxy&&) = delete;
    
        struct SUPER_FRIENDS { typedef T GO; };
        friend struct SUPER_FRIENDS::GO;
        unique_ptr<T> p;
    };
    
    struct Object {
        Object() : data(0) { }
    
        //Pseudo-copy constructor
        Object(ReturnProxy<Object>&& proxy)
          : data(proxy.p ? proxy.p->data : throw "Don't get sneaky with me \\glare") 
        {
          //steals `proxy.p` so that there isn't a second copy of this object floating around
          //shouldn't be necessary, but some men just want to watch the world burn.
          unique_ptr<Object> thief(std::move(proxy.p));
        }
    private:
        int data;
    
        Object(const Object&) = delete;
        void operator =(const Object&) = delete;
    };
    
    ReturnProxy<Object> func() {
        return ReturnProxy(new Object);
    }
    
    int main() {
        Object o(func());
    }
    

    You could probably do the same in 03, though, using auto_ptrs. And it obviously doesn't prevent storage of the resultant Object, although it does limit you to one copy per instance.

    0 讨论(0)
  • 2020-11-29 09:12

    Here is how it can work

    A f() {
      return { 10 };
    }
    

    This works even though A has no working copy or move constructor and no other constructor that could copy or move an A!

    To make use of this feature of C++11, the constructor (taking int in this case) has to be non-explicit though.

    0 讨论(0)
  • 2020-11-29 09:12

    The above code is still ill-formed in C++11. But you could add a public move constructor to A and then it would be legal:

    struct A
    {
        A(int x) {}
        A(A&&);
    private:
        A(A const&);
    };
    
    A f() {
      return A(10); // Ok!
    }
    
    0 讨论(0)
提交回复
热议问题