In Java we can declare a blank final
variable and initialize it later. The compiler will ensure initialization happens exactly once -- failure to initialize or double-initialization are both compile-time errors. For example:
public int bar() {
return 66;
}
public void foo() {
final int x; // declare the variable
...
x = bar(); // initialization only once
}
In Java, the compiler can guarantee that x
is definitely not assigned on any code path prior to its first assignment, and can guarantee that it is definitely never assigned a second time on any code path. (See Chapter 16, Definite Assignment, of the Java Language Specification for more information.)
How can we achieve similar behavior in C++? Is it possible to have a variable declared const
but defer its initialization? (Without casting away the const
specifier.)
It's not possible to initialize a const except where it's defined. You have to find a way to know it's value where it's defined. If x
's value is difficult to determine, consider using the result of a function like
const int x = calc_x();
or a closure like
const int x = []() { /* code to calculate x's value */ }();
const
ness is part of the type of an object and an object type's cannot be changed under any circumstance, therefore either x
is const
and you can't initialize it later or x
is not const
at all.
It's possible to design a wrapper class
that would emulate this, but you would at best get a run-time error.
Note that there may seem to be solution in the form of const_cast
but that assumes that the object in question is not actually const
. In the case of const int x
there is no way to legally change it's value after it's initialization.
C++ doesn't have a built in feature for this. You can kind of build it for yourself though. You can create a class that holds storage for an object of the type you want and you can overload the assignment operator for that so it can only be called and initialized once. That would look like
template<typename T>
class once
{
private:
std::aligned_storage_t<sizeof(T), alignof(T)> data;
T* ptr = nullptr;
public:
once() = default;
~once()
{
if(ptr) // it is initialized so call the destructor
ptr->~T();
// optionally you can add
// throw("a once<T> must be initialized once");
// this can help to enforce that the object is actually initialized as you'll get a runtime exception in code that does not do so
}
template<typename U>
once& operator =(U&& value)
{
if (!ptr) // it is not initialized so call constructor
{
ptr = new(&data) T(std::forward<U>(value));
}
else
throw ("can only assign to a once<T> once.");
return *this;
}
operator const T&()
{
return *ptr;
}
};
and then you would use it like
int main()
{
once<int> foo;
if (1 < -1)
foo = 21;
else
foo = 42;
std::cout << foo;
//foo = 23; // uncomment this to get an exception.
}
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