Let's start with some example code:
class Shader {
public:
static absl::StatusOr<Shader> Create(const std::string &vertexShaderFile, const std::string &fragmentShaderFile) {
ASSIGN_OR_RETURN(GLuint vertexShader, gl::createShader(GL_VERTEX_SHADER, readFile(vertexShaderFile)))
ASSIGN_OR_RETURN(GLuint fragmentShader, gl::createShader(GL_FRAGMENT_SHADER, readFile(fragmentShaderFile)))
Shader s;
glAttachShader(s._programId, vertexShader);
glAttachShader(s._programId, fragmentShader);
RETURN_IF_ERROR(gl::linkProgram(s._programId));
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
std::cerr << "AAA" << std::endl;
return s;
}
void use() const {
std::cerr << "use " << _programId << std::endl;
glUseProgram(_programId);
}
~Shader() {
std::cerr << "destruct!" << std::endl;
glDeleteProgram(_programId);
}
private:
explicit Shader() : _programId(glCreateProgram()) {}
GLuint _programId{0};
};
usage:
const auto &_statusOr4 = (Shader::Create("resources/vertex.glsl", "resources/frag.glsl"));
if (!_statusOr4.ok())return _statusOr4.status();
Shader shader = *_statusOr4;
std::cerr << "BBB" << std::endl;
This will print:
AAA
destruct!
BBB
I think the s
in Shader::Create
is being copied to a new object and then the old one is destructed, which calls the ~Shader
destructor.
I want to avoid the copy destruct.
I can't just throw all the Create
code in the c'tor because there's a few operations that might fail.
How can I do this?
Adding
Shader(const Shader&) = delete;
Shader& operator=(const Shader&) = delete;
Prevents this program from compiling. Which is fine, because this is indeed an error, but then I still don't know how to write such a Create
method (without moving everything into a constructor and throwing exceptions instead)
I tried to add a move constructor:
Shader(Shader&& o) noexcept : _programId(o._programId){
std::cerr << "move ctor" << std::endl;
}
Had to change the assignment to make it a reference:
const auto &_statusOr4 = (Shader::Create("resources/vertex.glsl", "resources/frag.glsl"));
if (!_statusOr4.ok()) return _statusOr4.status();
const Shader& shader = *_statusOr4;
Which I think should be fine because _statusOr4
will have the same lifetime.
However.... now I get
AAA
move ctor
destruct!
BBB
It seems the destructor is still called on the object that it was "moved" out of.
StatusOr
is from Abseil
CodePudding user response:
I want to avoid the copy destruct.
Normally, when your function return type matches the type of the variable you return, you don't need to do anything. There is no copying and no destruction taking place. Read about copy elision.
In your case, you construct Shader
but return absl::StatusOr<Shader>
. So there are two different objects: the local variable s
in your function, and the return value. They are of different types so the compiler cannot make them the same thing, and you will see the destructor of s
being called. However this is harmless if you write Shader
correctly. Support move construction, and s
will be moved to the return value, leaving behind an empty shell that is trivial to destroy. There is still no copying.
Assuming that 0 is an invalid/empty shader program and glDeleteProgram(0)
is essentially a no-op, the move constructor could look like this:
Shader(Shader&& other) {
_programId = other._programId;
other._programId = 0;
}
You don't want to freely copy objects Shader
objects, because otherwise you will end up deleting the same _programId
more than once. To avoid this, mark the copy constructor and the copy assignment operator as delete
.
I can't just throw all the Create code in the c'tor because there's a few operations that might fail.
That's what exceptions are for.