I read now unique_ptr source code in libstdc++.
public:
typedef _Tp* pointer;
typedef _Tp element_type;
typedef _Tp_Deleter deleter_type;
// Constructors.
unique_ptr()
: _M_t(pointer(), deleter_type())
{ static_assert(!std::is_pointer<deleter_type>::value,
"constructed with null function pointer deleter"); }
I don´t understand.Does "pointer()" call constructor? but "pointer" is an alias for type _Tp*
For all types, T() is an expression that value-initialises an unnamed instance of that type. For non-class, non-array types, value-initialisation is zero-initialisation
the object's initial value is the integral constant zero explicitly converted to T
And for a pointer type, that's a null pointer
This expression
pointer()
zero-initializes the data member pointer of the class that has a pointer type. For pointer types it means setting a pointer to a null pointer.
From the C++ 14 Standard (8.5 Initializers)
11 An object whose initializer is an empty set of parentheses, i.e.,
(), shall be value-initialized
and
8 To value-initialize an object of type T means:
(8.4) — otherwise, the object is zero-initialized.
Further
6 To zero-initialize an object or reference of type T means:
(6.1) — if T is a scalar type (3.9), the object is initialized to the
value obtained by converting the integer literal 0 (zero) to T;104
and in the footnote 104 there is written
As specified in 4.10, converting an integer literal whose value
is 0 to a pointer type results in a null pointer value
Related
The strict aliasing rule says
If a program attempts to access the stored value of an object through
a glvalue of other than one of the following types the behavior is
undefined:
— the dynamic type of the object,
— a cv-qualified version of the dynamic type of the object,
— a type similar (as defined in 4.4) to the dynamic type of the object,
— a type that is the signed or unsigned type corresponding to the dynamic type of the object,
— a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
— an aggregate or union type that includes one of the aforementioned types among its elements or nonstatic data members (including, recursively, an element or non-static data member of a subaggregate or contained union)
I am wondering if the following program already contains undefined behavior and if there is "an access to the stored value":
#include <cstdint>
void foo() {
std::int32_t i = 1;
float f = 1.0f;
std::int32_t& r = reinterpret_cast<std::int32_t&>(f);
std::int32_t* p = reinterpret_cast<std::int32_t*>(&f);
}
From what I see, the cast of the float pointer to the int reference is equivalent to `*reinterpret_cast(&x):
A glvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly
converted to the type “pointer to T2” using a reinterpret_cast The
result refers to the same object as the source glvalue, but with the
specified type. [ Note: That is, for lvalues, a reference cast
reinterpret_cast(x) has the same effect as the conversion
*reinterpret_cast(&x) with the built-in & and * operators (and similarly for reinterpret_cast(x)). —end note ]
For pointers, reinterpret_cast boils down to conversion to void* and then to the target type:
An object pointer can be explicitly converted to an object pointer of a different type.72 When a prvalue v of object pointer type is
converted to the object pointer type “pointer to cv T”, the result is
static_cast(static_cast(v)).
The semantics of the two static casts are defined as:
A prvalue of type “pointer to cv1 void” can be converted to a prvalue of type “pointer to cv2 T,” where T is an object type and cv2
is the same cv-qualification as, or greater cv-qualification than,
cv1. The null pointer value is converted to the null pointer value of
the destination type. If the original pointer value represents the
address A of a byte in memory and A satisfies the alignment
requirement of T, then the resulting pointer value represents the same
address as the original pointer value, that is, A. The result of any
other such pointer conversion is unspecified.
Since int32_t and float have same size and alignment, I should get a new pointer pointing to the same address. What I am wondering is if
a reference cast reinterpret_cast(x) has the same effect as the
conversion *reinterpret_cast(&x) with the built-in & and * operators
already constitutes an access to the stored value or if that must be made somewhere later to violate the strict aliasing rule.
The casts you made are not an access to the objects i or f.
3.1 access [defns.access]
⟨execution-time action⟩ to read or modify the value of an object
Since your program does not attempt to do the above, it does not violate strict aliasing. Trying to use those pointers/references to read or write will be a violation, however. So this is a fine line to tread. But merely obtaining the references/pointers is not against the first paragraph you stated. Casting to unrelated reference/pointer types only deals with an object's identity/address, and not its value.
Consider the code below:
#include <iostream>
#include <memory>
void f(std::shared_ptr<int> sp) {}
template <typename FuncType, typename PtrType>
auto call_f(FuncType f, PtrType p) -> decltype(f(p))
{
return f(p);
}
int main()
{
f(0); // doesn't work for any other int != 0, thanks #Rupesh
// call_f(f, 0); // error, cannot convert int to shared_ptr
}
In the first line in main(), the integer 0 is converted to a std::shared_ptr<int> and the call f(0) succeeds without any problem. However, using a template to call the function make things different. Second line will not compile anymore, the error being
error: could not convert 'p' from 'int' to 'std::shared_ptr<int>'
My questions are:
Why does the first call succeed and the second doesn't? Is there anything I'm missing here?
I don't understand also how the conversion from int to std::shared_ptr is being performed in the call f(0), as it looks like std::shared_ptr has only explicit constructors.
PS: A variant of this example appears in Scott Meyers' Effective Modern C++ Item 8, as a way of protecting such calls with nullptr.
std::shared_ptr has a constructor that takes std::nullptr_t, literal 0 is a null pointer constant that is convertiable to std::nullptr_t from the draft C++ standard section 4.10 [conv.ptr] (emphasis mine going forward):
A null pointer constant is an integral constant expression (5.19) prvalue of integer type that evaluates to zero or a prvalue of type
std::nullptr_t. A null pointer constant can be converted to a
pointer type; the result is the null pointer value of that type and is
distinguishable from every other value of object pointer or function
pointer type. Such a conversion is called a null pointer conversion.
Two null pointer values of the same type shall compare equal. The
conversion of a null pointer constant to a pointer to cv-qualified
type is a single conversion, and not the sequence of a pointer
conversion followed by a qualification conversion (4.4). A null
pointer constant of integral type can be converted to a prvalue of
type std::nullptr_t. [ Note: The resulting prvalue is not a null
pointer value. —end note ]
in your second case p is being deduced as type int which although has the value zero is no longer a null pointer constant and so does not fit the same case.
As T.C. points out the wording was changed with DR 903 which requires an integer literal with value zero as opposed to an integral constant expression which evaluates to zero:
A null pointer constant is an integer literal (2.14.2) with value
zero or a prvalue of type std::nullptr_t. A null pointer constant
can be converted to a pointer type; the result is the null pointer
value of that type and is distinguishable from every other value of
object pointer or function pointer type.
Per [conv.ptr]/1 (quoting N4296 here):
A null pointer constant is an integer literal (2.13.2) with value zero or a prvalue of type std::nullptr_t. ... A null pointer constant of integral type can be converted to a prvalue of type std::nullptr_t.
shared_ptr has a non-explicit constructor that accepts std::nullptr_t per [util.smartptr.shared.const]/1:
constexpr shared_ptr(nullptr_t) noexcept : shared_ptr() { }
which constructs an empty, non-owning shared_ptr.
When you call f(0) directly, 0 is a null pointer constant that is implicitly converted to shared_ptr<int> by the above constructor. When you instead call call_f(f, 0), the type of the literal 0 is deduced to int and of course an int cannot be converted to a shared_ptr<int>.
The firs call f(0) is compiled as f(nullptr), which is fine for the compiler (but it should not be in my opinion). The second call will create declaration for a function to work on any int, which is illegal.
Funny thing is, that even this code works:
f(3-3);
f(3*0);
What is the default value of a function pointer in C++? (Apparently it can't be NULL, so what is it?)
How is this program supposed to behave and why?
struct S { void (*f)(); };
int main()
{
S s = S();
s.f(); // What is the value of s.f?
}
First any pointer can be null. It is the one universal truth about pointers. That said, yours will be null, but not necessarily for the reasons you may think;
C++11 § 8.5,p10
An object whose initializer is an empty set of parentheses, i.e., (), shall be value-initialized.
This is important because your declaration includes this :
S s = S();
By the definition of value initialization:
C++11 § 8.5,p7
To value-initialize an object of type T means:
if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T’s implicitly-declared default constructor is non-trivial, that constructor is called.
if T is an array type, then each element is value-initialized;
otherwise, the object is zero-initialized.
Which brings us to what it means for your object-type to be zero-initialized:
C++11 § 8.5,p5
To zero-initialize an object or reference of type T means:
if T is a scalar type (3.9), the object is set to the value 0 (zero), taken as an integral constant expression, converted to T (103)
if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;
if T is a (possibly cv-qualified) union type, the object’s first non-static named data member is zero- initialized and padding is initialized to zero bits;
if T is an array type, each element is zero-initialized;
if T is a reference type, no initialization is performed.
103) As specified in 4.10, converting an integral constant expression whose value is 0 to a pointer type results in a null pointer value.
The latter is the reason you're pointer is null. It will not be guaranteed-so by the standard given the same code, but changing the declaration of s to this:
S s;
Given a declaration like the above, a different path is taken through the standard:
C++11 § 8.5,p11
If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, an object with automatic or dynamic storage duration has indeterminate value. [ Note: Objects with static or thread storage duration are zero-initialized, see 3.6.2.
Which then begs the last question, what is default initialization:
C++11 § 8.5,p6
To default-initialize an object of type T means:
if T is a (possibly cv-qualified) class type (Clause 9), the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
if T is an array type, each element is default-initialized;
otherwise, no initialization is performed.
In your case the object s is zero-initialized which means the function pointer is NULL.
struct S { void (*f)(); };
int main()
{
S s = S();
if ( s.f == NULL)
std::cout << "s.f is NULL" << std::endl;
}
Output:
s.f is NULL
Online demo.
A function pointer can be NULL and you may assign NULL to it. Have a look here for instance:
#include <iostream>
using namespace std;
struct S { void (*f)(); };
int main()
{
S s = S();
s.f = NULL;
return 0;
}
I believe the way you call the constructor of the structure(with ()), f will be NULL.
Function pointer can be NULL, this way you can indicate that they don't point to anything!
In C++ (and C), pointers (regardless of type) do not have a default value per se; they take what ever happens to be in memory at the time. However, they do have a default initialised value of NULL.
Default Initialisation
When you don't explicitly define a constructor, C++ will call the default initialiser on each member variable, which will initialise pointers to 0. However, if you define a constructor, but do not set the value for a pointer, it does not have a default value. The behaviour is the same for integers, floats and doubles.
Aside
int main()
{
S s = S();
s.f(); // <-- This is calling `f`, not getting the pointer value.
}
This question already has answers here:
What is the default constructor for C++ pointer?
(5 answers)
std::map default value
(16 answers)
Closed 10 years ago.
In my code, I have a std::map that looks something like this:
std::map<std::string, A*> myMap;
where A is one of my custom classes.
When I access a map element which does not exist via operator[] like so:
std::string s("hello");
A* pA = myMap[s];
I know that a new element will be created with that key but I would like the pointer to be initialized to NULL. In other words, if myMap[s] exists, a valid pointer should be returned. If not, I would like pA to be NULL after the above code executes.
By default, will pA contain garbage if myMap[s] doesn't exit? How can I make it so that pA contains the value NULL if the element doesn't exit?
Short Answer: Yes, your pointer can reliably be considered a nullptr on a new key lookup with no prior value.
Long Answer:
Per the standard:
C++11 § 23.4.4.3,p5
T& operator[](key_type&& x);
Effects: If there is no key equivalent to x in the map, inserts value_type(std::move(x), T()) into the map.
Note particularly the use of T(), where T in this case is your pointer type. This leads to...
C++11 § 8.5,p10
An object whose initializer is an empty set of parentheses, i.e., (), shall be value-initialized.
By the definition of value initialization:
C++11 § 8.5,p7
To value-initialize an object of type T means:
if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T’s implicitly-declared default constructor is non-trivial, that constructor is called.
if T is an array type, then each element is value-initialized;
otherwise, the object is zero-initialized.
Which brings us to what it means for your object-type to be zero-initialized:
C++11 § 8.5,p5
To zero-initialize an object or reference of type T means:
if T is a scalar type (3.9), the object is set to the value 0 (zero), taken as an integral constant expression, converted to T (103)
if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;
if T is a (possibly cv-qualified) union type, the object’s first non-static named data member is zero- initialized and padding is initialized to zero bits;
if T is an array type, each element is zero-initialized;
if T is a reference type, no initialization is performed.
103) As specified in 4.10, converting an integral constant expression whose value is 0 to a pointer type results in a null pointer value.
Yes new elements in map for embedded types like int or pointer are zero-initialized, so your pointer will be NULL.
In ISO/IEC 9899:TC2, the standard says following
6.3.2.3 Pointers
A pointer to an object or incomplete type may be converted to a pointer to a different
object or incomplete type. If the resulting pointer is not correctly aligned for the pointed-to type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer. When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of
the object. Successive increments of the result, up to the size of the object, yield pointers to the remaining bytes of the object.
So, it is not clear from the standard that a pointer of one type can be casted to pointer of another type.
Strict aliasing rule is defined somewhere else. This is the wording:
C (ISO/IEC 9899:1999 6.5/7):
An object shall have its stored value accessed only by an lvalue
expression that has one of the following types:
a type compatible with the effective type of the object,
a qualified version of a type compatible with the effective type of
the object,
a type that is the signed or unsigned type corresponding to the
effective type of the object,
a type that is the signed or unsigned type corresponding to a
qualified version of the effective type of the object,
an aggregate or union type that includes one of the aforementioned
types among its members (including, recursively, a member of a
subaggregate or contained union), or
a character type.
C++ (ISO/IEC 14882:2011 3.10 [basicl.lval] / 15):
If a program attempts to access the stored value of an object through
an lvalue of other than one of the following types the behavior is
undefined:
the dynamic type of the object,
a cv-qualified version of the dynamic type of the object,
a type similar (as defined in 4.4) to the dynamic type of the object,
a type that is the signed or unsigned type corresponding to the
dynamic type of the object,
a type that is the signed or unsigned type corresponding to a
cv-qualified version of the dynamic type of the object,
an aggregate or union type that includes one of the aforementioned
types among its elements or non-static data members (including,
recursively, an element or non-static data member of a subaggregate
or contained union),
a type that is a (possibly cv-qualified) base class type of the
dynamic type of the object,
a char or unsigned char type.
The C standard doesn't prohibit you from casting the pointer to an unrelated type, provided there are no allignment problems. However, due to the strict aliasing rule, you basically can't dereference a pointer obtained from such a cast. So the only useful thing to do with such "invalid" pointer is to cast it back to the correct type (or a compatible type).
It's mostly the same in C++ with reinterpret_cast (5.2.10 [expr.reinterpret.cast] / 7):
An object pointer can be explicitly converted to an object pointer of a different type. When a prvalue v of type “pointer to T1” is converted to the type “pointer to cv T2”, the result is static_cast<cv T2*>(static_cast<cv void*>(v)) if both T1 and T2 are standard-layout types (3.9) and the alignment requirements of T2 are no stricter than those of T1, or if either type is void. Converting a prvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value. The result of any other such pointer conversion is unspecified.