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.
Named return value optimisation is a non-mandatory copy elision. This means the object returned must have an accessible, non-deleted copy or move constructor.
Citing cppreference.com:
Non-mandatory copy/move (since C++11) elision
Under the following circumstances, the compilers are permitted, but not required to omit the copy and move (since C++11) construction of class objects even if the copy/move (since C++11) constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. This is an optimization: even when it takes place and the copy/move (since C++11) constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed:
- In a return statement, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a function parameter or a catch clause parameter, and which is of the same class type (ignoring cv-qualification) as the function return type. This variant of copy elision is known as NRVO, "named return value optimization."
Citing the Standard [class.copy.elision]/1:
When certain criteria are met, an implementation is allowed to omit the copy/move construction [...]
- in a return statement in a function with a class return type, when the expression is the name of a non-volatile object with automatic storage duration [...]
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With