Search code examples
c++return-value-optimization

Why isn't named return value optimization working here?


I know that I have deleted the copy constructor, I was assuming that was OK since I was expecting named return value optimization and that direct initialisation would happen. Does the copy constructor need to be declared? If I do I have the problem that a member of the class can't be copied either, so what do I do?

class UniqueBufferPointer
{public:
    UniqueBufferPointer() {}
    UniqueBufferPointer(const UniqueBufferPointer& other) = delete;
    UniqueBufferPointer(UniqueBufferPointer&& other) {}
    UniqueBufferPointer& operator=(const UniqueBufferPointer&) = delete;

};


struct GFXAPIImage
{
    GFXAPIImage() {}

    UniqueBufferPointer handle;
    GFXAPIImage(const GFXAPIImage&) = delete;
    //GFXAPIImage(const GFXAPIImage& other) = default; // If I use this instead of the compiler complaining that it can't access the copy constructor of this class, it complains it can't access the copy constructor of UniqueBufferPointer.

};


GFXAPIImage func()
{
    GFXAPIImage f;
    return f; //GFXAPIImage(const GFXAPIImage&) cannot be referenced, it is a deleted function
}


int main()
{

    GFXAPIImage f = func();
}

I'm on Visual STudio and compiling with /O2 optimization flag.

Edit: Also if I declare a move constructor for GFXAPIImage it compiles. Does this mean it uses the move constructor? Why? It's an l-value it should have no business moving it.


Solution

  • By explicitly deleting GFXAPIImage's copy constructor, you're preventing the compiler from supplying a implicitly-defined default move constructor.

    This is a problem, since when you write return f, the object f needs to be either copied or moved. But without a move constructor, it can't be moved, and with a deleted copy-constructor, it can't be copied. Boom: compiler error.

    Note that NVRO is not actually relevant here. What NVRO means is that the compiler is allowed to optionally elide the call to GFXAPIImage::GFXAPIImage(GFXAPIImage&&) when returning a named object from a function. But this is only an optimization. You still need GFXAPIImage::GFXAPIImage(GFXAPIImage&&) to exist in the first place for the code to be well formed.

    To fix your code, you can simply remove the explicitly deleted copy constructor from GFXAPIImage. This will enable the compiler to supply an implicitly-defined default move constructor, which will make GFXAPIImage move-able again. Optionally, you could instead define your own move constructor, or request the compiler to supply a default move constructor even with the deleted copy constructor with = default.

    Note that even if you don't explicitly deleted the copy constructor, it will still be implicitly deleted. So you don't lose any safety by omitting the explicit deletion.

    See also Conditions for automatic generation of default/copy/move ctor and copy/move assignment operator?

    struct GFXAPIImage
    {
        GFXAPIImage() {}
    
        UniqueBufferPointer handle;
        // no explicitly deleted copy constructor
    
    };
    
    
    GFXAPIImage func()
    {
        GFXAPIImage f;
        return f; //  OKAY, calls move constructor
    }
    
    int main()
    {
    
        GFXAPIImage f = func();
        // GFXAPIImage g = f; // would fail due to GFXAPIImage(const GFXAPIImage&) being implicitly deleted
    }