I am developing a UE plugin, and uses goto statement like this: (To make it reproducible, I turn the UE things into std) I’m using constexpr to replace MACRO, but it seems that it introduce variable initialization?
#include <iostream>
#include <fstream>
void PrintCircleInfo(float radius, bool shouldPrintArea){
std::ofstream outFile("example.txt");
float diameter = radius * 2;
outFile << "diameter " << diameter;
if(!shouldPrintArea){
goto CLEAN_UP_AND_RETURN;
}
constexpr double PI = 3.14;//clang don't allow bypass this line
outFile << "area " << PI * radius * radius;
CLEAN_UP_AND_RETURN:
outFile.close();
}
When I start it through VS, MSVC is OK with it. But When I package it to Android Platform, clang complains about it(jump bypasses variable initialization), which is unexpected?
MSVC and clang behave differently. Who is wrong and why?
4
A variable marked constexpr
is still a normal variable with normal lifetime. It is initialized when control passes through it and it is destroyed when control leaves its scope. constexpr
only makes it so that the value of the variable can be used in constant expressions at compile-time independently of the variable’s lifetime.
In particular you could e.g. take the address of PI
and if your function was entered recursively, in each call PI
would have a different address. If you do not need this you can make the variable static constexpr
instead, so that there will be only one instance of it during program execution. constexpr
will guarantee that it is constant-initialized at program start.
[stmt.dcl]/2 forbids making a jump that would jump over the initialization of a variable with automatic storage duration (i.e. not static
) that has non-vacuous initialization. Initialization is vacuous if the initialization is default-initialization and trivial (e.g. double PI;
). In your case you do not use default-initialization and so it isn’t vacuous initialization. That the variable’s initialization is known at compile-time doesn’t matter.
MSVC also behaves according to the standard when given the /permissive-
flag for standard-conformance.
Your example might not reflect the actual logic, but regardless:
There is no need to define PI
like this. Since C++20 it is present (with correct value) in the standard library as std::numbers::pi
.
Even if it needs to be defined, it is a mathematical constant that probably belongs somewhere more global than a function scope.
The goto
cleanup pattern is not idiomatic in C++ and generally even wrong. It can’t handle exceptions correctly. The only way to make sure that cleanup is performed even if an exception is thrown is to perform the cleanup in the destructor of a local variable. Each variable should cleanup resources it acquired in its destructor (that’s the idea behind RAII).
For example, std::ofstream
acquires a handle to an open file and therefore should close the file handle in its destructor and that’s exactly what std::ofstream
‘s destructor is specified to do. It is never necessary to call .close()
manually, except if you want a file to be closed early but continue working in the ofstream
‘s scope (although arguably you should try to narrow the scope of the ofstream
as much as possible).