From 1a78a62555be32868418fe52f8e330c9d0f95d5a Mon Sep 17 00:00:00 2001
From: Anas Nashif Constructing and initializing objects in a generic way is difficult in
+ C++. The problem is that there are several different rules that apply
+for initialization. Depending on the type, the value of a newly constructed
+ object can be zero-initialized (logically 0), default-constructed (using
+ the default constructor), or indeterminate. When writing generic code,
+this problem must be addressed. The template
+There are various ways to initialize a variable, in C++. The following
+declarations all may have a local variable initialized to its default
+value:
+
+ Header <boost/utility/value_init.hpp>
+
+
+Contents
+
+
+
+
+
+
+
+
+
+ Acknowledgements
+
+
+
+Rationale
+
+value_initialized
provides
+a solution with consistent syntax for value initialization of scalar,
+union and class types.
+Moreover, value_initialized
offers a workaround to various
+compiler issues regarding value-initialization.
+
+Furthermore, a const
object, initialized_value
is provided,
+to avoid repeating the type name when retrieving the value from a
+value_initialized<T>
object.
+
+ Introduction
+
+
+ T1 var1;
+ T2 var2 = 0;
+ T3 var3 = {};
+ T4 var4 = T4();
+
+Unfortunately, whether or not any of those declarations correctly
+initialize the variable very much depends on its type. The first
+declaration is valid for any
+DefaultConstructible type (by definition).
+However, it does not always do an initialization!
+It correctly initializes the variable when it's an instance of a
+class, and the author of the class has provided a proper default
+constructor. On the other hand, the value of var1
is indeterminate when
+its type is an arithmetic type, like int
, float
, or char
.
+An arithmetic variable
+is of course initialized properly by the second declaration, T2
+var2 = 0
. But this initialization form usually won't work for a
+class type (unless the class was especially written to support being
+initialized that way). The third form, T3 var3 = {}
+initializes an aggregate, typically a "C-style" struct
or a "C-style" array.
+However, the syntax is not allowed for a class that has an explicitly declared
+constructor. (But watch out for an upcoming C++ language change,
+by Bjarne Stroustrup et al [1]!)
+The fourth form is the most generic form of them, as it
+can be used to initialize arithmetic types, class types, aggregates, pointers, and
+other types. The declaration, T4 var4 = T4()
, should be read
+as follows: First a temporary object is created, by T4()
.
+This object is value-initialized. Next the temporary
+object is copied to the named variable, var4
. Afterwards, the temporary
+is destroyed. While the copying and the destruction are likely to
+be optimized away, C++ still requires the type T4
to be
+CopyConstructible.
+(So T4
needs to be both DefaultConstructible and CopyConstructible.)
+A class may not be CopyConstructible, for example because it may have a
+private and undefined copy constructor,
+or because it may be derived from boost::noncopyable.
+Scott Meyers [2] explains why a class would be defined like that.
+
+There is another, less obvious disadvantage to the fourth form, T4 var4 = T4()
:
+It suffers from various compiler issues, causing
+a variable to be left uninitialized in some compiler specific cases.
+
+The template value_initialized
+offers a generic way to initialize
+an object, like T4 var4 = T4()
, but without requiring its type
+to be CopyConstructible. And it offers a workaround to those compiler issues
+regarding value-initialization as well! It allows getting an initialized
+variable of any type; it only requires the type to be DefaultConstructible.
+A properly value-initialized object of type T
is
+constructed by the following declaration:
+
+ value_initialized<T> var; ++ +
+The template initialized
+offers both value-initialization and direct-initialization.
+It is especially useful as a data member type, allowing the very same object
+to be either direct-initialized or value-initialized.
+
+The const
object initialized_value
+allows value-initializing a variable as follows:
+
+ T var = initialized_value ; ++This form of initialization is semantically equivalent to
T4 var4 = T4()
,
+but robust against the aforementioned compiler issues.
+
+
+
+The C++ standard [3] contains the definitions
+ of zero-initialization
and default-initialization
.
+ Informally, zero-initialization means that the object is given the initial
+ value 0 (converted to the type) and default-initialization means that
+ POD [4] types are zero-initialized, while non-POD class
+ types are initialized with their corresponding default constructors. A
+declaration can contain an initializer, which specifies the
+object's initial value. The initializer can be just '()', which states that
+the object shall be value-initialized (but see below). However, if a declaration
+ has no initializer and it is of a non-const
, non-static
+ POD type, the initial value is indeterminate: (see §8.5, [dcl.init], for the
+ accurate definitions).
int x ; // no initializer. x value is indeterminate.+ +
std::string s ; // no initializer, s is default-constructed.
int y = int() ;
// y is initialized using copy-initialization
// but the temporary uses an empty set of parentheses as the initializer,
// so it is default-constructed.
// A default constructed POD type is zero-initialized,
// therefore, y == 0.
void foo ( std::string ) ;
foo ( std::string() ) ;
// the temporary string is default constructed
// as indicated by the initializer ()
The first Technical + Corrigendum for the C++ Standard (TC1), whose draft was released to + the public in November 2001, introduced Core + Issue 178 (among many other issues, of course).
+ + That issue introduced the new concept of value-initialization
+ (it also fixed the wording for zero-initialization). Informally, value-initialization
+ is similar to default-initialization with the exception that in some cases
+ non-static data members and base class sub-objects are also value-initialized.
+ The difference is that an object that is value-initialized won't have
+(or at least is less likely to have) indeterminate values for data members
+ and base class sub-objects; unlike the case of an object default constructed.
+ (see Core Issue 178 for a normative description).
In order to specify value-initialization of an object we need to use the + empty-set initializer: ().
+ +As before, a declaration with no intializer specifies default-initialization, + and a declaration with a non-empty initializer specifies copy (=xxx) or + direct (xxx) initialization.
+ +template<class T> void eat(T);+ +
int x ; // indeterminate initial value.
std::string s; // default-initialized.
eat ( int() ) ; // value-initialized
eat ( std::string() ) ; // value-initialized
Value initialization is specified using (). However, the empty set of +parentheses is not permitted by the syntax of initializers because it is +parsed as the declaration of a function taking no arguments:
+ +int x() ; // declares function int(*)()+ +
Thus, the empty () must be put in some other initialization context.
+ +One alternative is to use copy-initialization syntax:
+ +int x = int() ;+ +
This works perfectly fine for POD types. But for non-POD class types, +copy-initialization searches for a suitable constructor, which could be, +for instance, the copy-constructor (it also searches for a suitable conversion +sequence but this doesn't apply in this context). For an arbitrary unknown +type, using this syntax may not have the value-initialization effect intended +because we don't know if a copy from a default constructed object is exactly +the same as a default constructed object, and the compiler is allowed (in +some cases), but never required to, optimize the copy away.
+ +One possible generic solution is to use value-initialization of a non static +data member:
+ +template<class T>+ +
struct W
{
// value-initialization of 'data' here.
W() : data() {}
T data ;
} ;
W<int> w ;
// w.data is value-initialized for any type.
This is the solution as it was supplied by earlier versions of the
+value_initialized<T>
template
+ class. Unfortunately this approach suffered from various compiler issues.
+At the moment of writing, May 2010, the following reported issues regarding +value-initialization are still there in current compiler releases: +
+
+New versions of value_initialized
+(Boost release version 1.35 or higher)
+offer a workaround to these issues: value_initialized
may now clear
+its internal data, prior to constructing the object that it contains. It will do
+so for those compilers that need to have such a workaround, based on the
+compiler defect macro BOOST_NO_COMPLETE_VALUE_INITIALIZATION.
+
template class value_initialized<T>
namespace boost {+ +
template<class T>
class value_initialized
{ +
public : +
value_initialized() : x() {} +
operator T const &() const { return x ; } +
operator T&() { return x ; } +
T const &data() const { return x ; } +
T& data() { return x ; } +
void swap( value_initialized<T>& ); +
+
private : +
unspecified x ; +
} ; +
+
template<class T> +
T const& get ( value_initialized<T> const& x ) +
{ +
return x.data() ; +
} +
+
template<class T> +
T& get ( value_initialized<T>& x ) +
{ +
return x.data() ; +
} +
+
} // namespace boost +
An object of this template class is a T
-wrapper convertible
+ to 'T&'
whose wrapped object (data member of type T
)
+ is value-initialized upon default-initialization
+ of this wrapper class:
int zero = 0 ;+ +
value_initialized<int> x ;
assert ( x == zero ) ;
std::string def ;
value_initialized< std::string > y ;
assert ( y == def ) ;
The purpose of this wrapper is to provide a consistent syntax for value + initialization of scalar, union and class types (POD and non-POD) since + the correct syntax for value initialization varies (see value-initialization syntax)
+ +The wrapped object can be accessed either through the conversion operator
+ T&
, the member function data()
, or the
+non-member function get()
:
void watch(int);+ +
value_initialized<int> x; +
watch(x) ; // operator T& used.
watch(x.data());
watch( get(x) ) // function get() used
Both const
and non-const
objects can be wrapped.
+ Mutable objects can be modified directly from within the wrapper but constant
+ objects cannot:
When T
is a Swappable type, value_initialized<T>
+ is swappable as well, by calling its swap
member function
+ as well as by calling boost::swap
.
value_initialized<int> x ;+ +
static_cast<int&>(x) = 1 ; // OK
get(x) = 1 ; // OK +
value_initialized<int const> y ;
static_cast<int&>(y) = 1 ; // ERROR: cannot cast to int&
static_cast<int const&>(y) = 1 ; // ERROR: cannot modify a const value
get(y) = 1 ; // ERROR: cannot modify a const value
The value_initialized
implementation of Boost version 1.40.0 and older
+allowed non-const access to the wrapped object, from a constant wrapper,
+both by its conversion operator and its data()
member function. For example:
value_initialized<int> const x_c ;+ +
int& xr = x_c ; // OK, conversion to int& available even though x_c is itself const. +
xr = 2 ;
The reason for this obscure behavior was that some compilers + didn't accept the following valid code:
+ +struct X+ +
{
operator int&() ;
operator int const&() const ;
};
X x ;
(x == 1 ) ; // ERROR HERE!
The current version of value_initialized
no longer has this obscure behavior.
+As compilers nowadays widely support overloading the conversion operator by having a const
and a non-const
version, we have decided to fix the issue accordingly. So the current version supports the idea of logical constness.
+
+
The obscure behavior of being able to modify a non-const
+wrapped object from within a constant wrapper (as was supported by previous
+versions of value_initialized
)
+can be avoided if access to
+the wrapped object is always performed with the get()
idiom:
value_initialized<int> x ;+ +
get(x) = 1 ; // OK
value_initialized<int const> cx ;
get(x) = 1 ; // ERROR: Cannot modify a const object
value_initialized<int> const x_c ;
get(x_c) = 1 ; // ERROR: Cannot modify a const object
value_initialized<int const> const cx_c ;
get(cx_c) = 1 ; // ERROR: Cannot modify a const object
template class initialized<T>
namespace boost {+ +The template class
template<class T>
class initialized
{ +
public : +
initialized() : x() {} +
explicit initialized(T const & arg) : x(arg) {} +
operator T const &() const; +
operator T&(); +
T const &data() const; +
T& data(); +
void swap( value_initialized<T>& ); +
+
private : +
unspecified x ; +
} ; +
+
template<class T> +
T const& get ( initialized<T> const& x ); +
+
template<class T> +
T& get ( initialized<T>& x ); +
+
} // namespace boost +
boost::initialized<T>
supports both value-initialization
+and direct-initialization, so its interface is a superset of the interface
+of value_initialized<T>
: Its default-constructor
+value-initializes the wrapped object just like the default-constructor of
+value_initialized<T>
, but boost::initialized<T>
+also offers an extra explicit
+constructor, which direct-initializes the wrapped object by the specified value.
+
+
+initialized<T>
is especially useful when the wrapped
+object must be either value-initialized or direct-initialized, depending on
+runtime conditions. For example, initialized<T>
could
+hold the value of a data member that may be value-initialized by some
+constructors, and direct-initialized by others.
+On the other hand, if it is known beforehand that the
+object must always be value-initialized, value_initialized<T>
+may be preferable. And if the object must always be
+direct-initialized, none of the two wrappers really needs to be used.
+
initialized_value
+namespace boost { +class initialized_value_t +{ + public : + template <class T> operator T() const ; +}; + +initialized_value_t const initialized_value = {} ; + +} // namespace boost ++ +
initialized_value
provides a convenient way to get
+an initialized value: its conversion operator provides an appropriate
+value-initialized object for any CopyConstructible type.
+
+Suppose you need to have an initialized variable of type T
.
+You could do it as follows:
++ T var = T(); ++But as mentioned before, this form suffers from various compiler issues. +The template
value_initialized
offers a workaround:
++ T var = get( value_initialized<T>() ); ++Unfortunately both forms repeat the type name, which +is rather short now (
T
), but could of course be
+more like Namespace::Template<Arg>::Type
.
+Instead, one could use initialized_value
as follows:
++ T var = initialized_value ; ++ +
var
of any DefaultConstructible type
+ T
to be value-initialized by doing T var = {}
.
+ The papers are listed at Bjarne's web page,
+ My C++ Standards committee papers value_initialized was reimplemented by Fernando Cacciola and Niels Dekker +for Boost release version 1.35 (2008), offering a workaround to various compiler issues. +
+boost::initialized
was very much inspired by feedback from Edward Diener and
+ Jeffrey Hellrung.
+
initialized_value was written by Niels Dekker, and added to Boost release version 1.36 (2008). +
+Developed by Fernando Cacciola, + the latest version of this file can be found at www.boost.org. +
+ +Revised 30 May 2010
+ +© Copyright Fernando Cacciola, 2002 - 2010.
+ +Distributed under the Boost Software License, Version 1.0. See +www.boost.org/LICENSE_1_0.txt
+ +