0

Suppose I have an object X and a proxy. I want to control lifetime of the proxy so that it cannot be extended beyond temporary object, returned by a method of the X. I can't understand if the way I did it is due to what standard allows.

class X
{
public:
    struct Proxy
    {
        ~Proxy();

        /// Implicit convertion to wrapped object.
        operator X &();
        operator const X &() const;

        /// Explicit access to wrapped object.
        X & get();
        const X & get() const;

    private:
        friend class X;

        Proxy(X & x_ref);
        Proxy(Proxy && other) noexcept;

        Proxy(const Proxy &) = delete;
        Proxy & operator = (const Proxy&) = delete;
        Proxy & operator = (Proxy && other) = delete;

        X & _x_ref;
    };

    Proxy get_proxy() {
        Proxy proxy(*this);
        // ...
        return proxy;
    }
};

Specifically I am worried about the part when an object is returned from the X::get_proxy() method. Technically, who calls the move constructor, to move an object from the body of the method into temporary object, that is a return value of this method? Does the standard says it is done in a scope of X, so friend specifier works correctly here?

It compiles without neither warnings nor errors with GCC 4.7-4.9 and clang 3.0-3.5. But I just need confirmation from the 3rd party that this won't change in the future, because it is a standardized behavior.

Note: this code example may seem silly, but it is a stripped, general case example. In the real world application proxy is used to expose some internal data of the X, not the reference to X.

Note: here I use move constructor since it's a future and it's time to move on and forget about old standards. But this proxy could be made with a copy constructor instead. Though this does not change the question. Except if things has changed in this scope in between c++03 and c++11/14.

GreenScape
  • 7,191
  • 2
  • 34
  • 64
  • @KerrekSB, `std::set` won't work, there is no `operator<`. Neither would `std::unordered_set`. Both `std::vector` and `std::list` whine that `X::Proxy` has private move constructor. It is a predictable behavior. – GreenScape Dec 17 '14 at 09:39
  • @KerrekSB, please, read my comment. Neither `std::vector` nor `std::list` work. Move constructor of `X::Proxy` is private: *error: field of type 'X::Proxy' has private move constructor* – GreenScape Dec 17 '14 at 10:00
  • Yes, you're right. Never mind. One way to leak would be `auto && x = X().get_proxy();`, but you could fix that by lvalue-qualifying the `get_proxy` member. – Kerrek SB Dec 17 '14 at 10:05
  • @KerrekSB, yes, this can be done. But it is acceptable, for it cannot extend `Proxy` lifetime beyond the scope where `X::get_proxy` is called. – GreenScape Dec 17 '14 at 10:22

1 Answers1

0

Consider this general function definition:

struct T { /* ... */ };   // to be sure that "T" is an object type

T foo()
{
    T x(1, 2, 3);
    return x;
}

The function foo returns a prvalue of type T. Now the function evaluation foo() contains, formally, two constructors: First the construction of x, and then the construction of the return value, which is copy-or-move-constructed from x (depending on the details of the type T).

Finally, when you initialize a variable somewhere else as T a = foo();, there is a third formal constructor call, namely the copy-construction of a from the temporary. (Copy elision allows both copy constructors to be omitted, and the effect may be just as if you had said T a(1, 2, 3);)

All the constructors in the above situations have to be accessible at the point where they are formally called. None of the calls, both in my shortened example and in your original code, are inside member functions of T, so if the constructors are private, then the call site needs to be a friend of the class.

Your code looks like it is doing this correctly.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • You see, the thing is that I am not sure that *and then the construction of the return value, which is copy-or-move-constructed from x (depending on the details of the type T)* is being formally called from within the body of `foo()` and whether standard clearly defines it. – GreenScape Dec 17 '14 at 11:31