When I run this code, the VS compiler return error and says that t1.mem is uninitialized local variable.
#include <string>
#include <iostream>
struct T1
{
int mem;
};
struct T2
{
int mem;
T2() { } // "mem" is not in the initializer list
};
int main()
{
T1 t1; // class, calls implicit default ctor
std::cout << t1.mem << std::endl;
const T2 t2; // const class, calls the user-provided default ctor
// t2.mem is default-initialized (to indeterminate value)
std::cout << t2.mem << std::endl;
}
If I have not assigned the constructor for struct T1, the compiler would have to generate the default constructor? And struct T2's constructor is empty initialization list, why it has no error tips?
My understanding is that the compiler is trying to protect you from its own generated code, and assumes "you know best" when using your provided constructor. In addition, checking whether or not your constructor actually ends up initializing T2.mem anywhere, including in the body of the constructor, could be an arbitrarily complex task, so the compiler authors may have decided that was a task better left unattempted than poorly executed.
This seems to be supported by the warning you would get from MSVC if you declared t1 as a const T1:
'const' automatic data initialized with compiler generated default constructor produces unreliable results
Note the wording "compiler generated default constructor".
Btw, you'll see this same warning if you request the compiler-generated default constructor with T2() = default.
Well, compilers aren't perfect. Sometimes they warn for one thing, but they don't for another, similar thing. Many compilers also offer runtime instrumentation of the generated code, where they insert special instructions that detect errors like use of uninitialized variables and will abort the program when that happens. But again, the system is not perfect and it can miss things.
In any event, you don't actually need a constructor. You can inline-initialize the class members:
struct T1
{
int mem = 0;
};
The inline initialization will be used by default, unless a constructor initializes the member to something else:
struct T1
{
int mem = 0;
T1() = default;
T1(int m) : mem(m) { }
};
// ...
T1 first; // first.mem == 0
T1 second(1); // second.mem == 1
Related
This question already has an answer here:
Why do tuples not get unused variable warnings?
(1 answer)
Closed 10 months ago.
I noticed in a PR review an unused variable and we were wondering why compiler didn't catch that. So I tested with godbolt the following code with bunch of unused variables and was surprised that some were reported as unused but others not. Even though all of them are unused.
#include <string>
struct Index
{
Index(int index) : m_index(index) {}
int m_index;
};
int main()
{
std::string str = "hello"; // case 1. no warning here - unexpected
int someValue = 2; // case 2. warning - as expected
const int someConstant = 2; // case 3. warning - as expected
Index index1(2); // case 4. just as equally not used but no warning - unexpected
// here using the assignment but do get a warning here
// but the str assignment doesn't give a warning - weird
Index index2 = 2; // case 5.
Index index3{2}; // case 6. just as equally not used but no warning - unexpected
Index index4 = {2}; // case 7. just as equally not used but no warning - unexpected
return 0;
}
warning: unused variable 'someValue' [-Wunused-variable]
warning: unused variable 'index2' [-Wunused-variable] (warning only on clang, not on gcc)
warning: unused variable 'someConstant' [-Wunused-variable]
So what do clang and gcc qualify as unused? What if I'm using a lock? I declare it but don't use it directly but use it for automatic releasing of a resource. How do I tell the compiler that I am using it if one day it starts to give a warning about the lock?
int g_i = 0;
std::mutex g_i_mutex; // protects g_i
void safe_increment()
{
const std::lock_guard<std::mutex> lock(g_i_mutex);
++g_i;
// g_i_mutex is automatically released when lock goes out of scope
}
flags: -Wunused-variable
clang: 14.0.0
gcc: 11.3
The reason why there's no warning is that variables of non-trivial class type aren't technically unused when you initialize them but then never access them in your function.
Consider this example:
struct Trivial {};
struct NonTrivial {
NonTrivial() {
//Whatever
}
};
void test() {
Trivial t;
NonTrivial nt;
}
GCC warns about Trivial t; being unused since this declaration never causes any user-defined code to run; the only thing that's run are the trivial constructor and trivial destructor, which are no-ops. So no operation at all is performed on Trivial t and it is truly unused (its memory is never even touched).
NonTrivial nt; doesn't cause a warning, however, since it is in fact used to run its constructor, which is user-defined code.
That's also why compilers are not going to warn about "unused lock guards" or similar RAII classes - they're used to run user-defined code at construction and destruction, which means that they are used (a pointer to the object is passed to the user-defined constructor/destructor = address taken = used).
This can further be proved by marking the object's constructor with the gnu::pure attribute:
struct Trivial {};
struct NonTrivial {
[[gnu::pure]] NonTrivial() {
//Whatever
}
};
void test() {
Trivial t;
NonTrivial nt;
}
In this case, GCC warns about both of them because it knows that NonTrivial::NonTrivial() doesn't have side-effects, which in turn enables the compiler to prove that construction and destruction of a NonTrivial is a no-op, giving us back our "unused variable" warning. (It also warns about gnu::pure being used on a void function, which is fair enough. You shouldn't usually do that.)
Clang's warning about the following code also does make sense.
struct Hmm {
int m_i;
Hmm(int i): m_i(i) {}
};
void test() {
Hmm hmm = 2; //Case 5 from the question
}
This is equivalent to the following:
void test() {
Hmm hmm = Hmm(2);
}
Construction of the temporary Hmm(2) has side-effects (it calls the user-defined constructor), so this temporary is not unused. However, the temporary then gets moved into the local variable Hmm hmm. Both the move constructor and the destructor of that local variable are trivial (and therefore don't invoke user code), so the variable is indeed unused since the compiler can prove that the behavior of the program would be the same whether or not that variable is present (trivial ctor + trivial dtor + no other access to the variable = unused variable, as explained above). It wouldn't be unused if Hmm had a non-trivial move constructor or a non-trivial destructor.
Note that a trivial move constructor leaves the moved-from object intact, so it truly does not have any side-effects (other than initializing the object that's being constructed).
This can easily be verified by deleting the move constructor, which causes both Clang and GCC to complain.
Here is an example from cpp reference.
struct T1 { int mem; };
struct T2
{
int mem;
T2() { } // "mem" is not in the initializer list
};
int main()
{
// const T1 t1; // error: const class with implicit default ctor
T1 t1; // class, calls implicit default ctor
const T2 t2; // const class, calls the user-provided default ctor
// t2.mem is default-initialized (to indeterminate value)
}
In the example, // const T1 t1; // error: const class with implicit default ctor makes sense because if that was allowed then it would make mem an uninitialized const member that cant be changed.
But the third one,
const T2 t2; // const class, calls the user-provided default ctor
// t2.mem is default-initialized (to indeterminate value)
It says, // const class, calls the user-provided default ctor. Now this doesn't make sense to me because the constructor still isn't initializing mem. Then it goes on to say // t2.mem is default-initialized. How?
I would understand this if the constructor initialized mem. Something like:
T2(int mem = 0) : mem(mem) { }
The original user defined constructor in the cpp reference example does absolutely nothing and still initializes mem. How? Is it just a rule I need to remember or is there something else happening?
Why does a user-provided constructor allow for instantiation of a const class instance?
Because the default constructor is responsible for initialisation of the object.
Sure, in this case the constructor fails to initialise the member in this case, but the compiler cannot generally know that whether it does that. Because a user defined constructor is used, it may make sense and thus there is no reason to disallow that.
The original user defined constructor in the cpp reference example does absolutely nothing and still initializes mem
It "default initialises" which is what happens when you don't initialise something.
I am writing a generic interface for scripting languages and need to call functions that return an object and put the resulting object in a space provided by the scripting language.
I have tested with clang++ on Mac OS and the following seems to do the copy/move elision:
class T {
public:
T() {}
T(const T &t) {
std::cout << "Copy" << std::endl;
}
T(T&&t) : x(t.x) {
std::cout << "Move" << std::endl;
}
private:
int x = 3;
};
T h()
{
return T();
}
void buildTInto(void *p)
{
new (p) T(h());
}
int main(int argc, char *argv[])
{
alignas(T) char space[sizeof(T)];
buildTInto(space);
}
Running this code does not print anything.
Reading the cpp reference description of copy elision seems to indicate this is the correct behavior, given the example they give which only calls one constructor:
T x = T(T(f())); // only one call to default constructor of T, to initialize x
I think my code is very much similar as without copy elision, the move constructor would be called (just like the T(T(f())) would call two move construction).
Not being a spec lawyer, I am curious if my understanding is correct and I wonder if all C++17 capable compilers do this correctly?
Guaranteed elision (aka: C++17's definition of prvalues) applies to the initialization of any object of type T with a prvalue of the same type. That includes objects created by new expressions, including placement new forms.
Indeed, you don't even have to repeat the type at the point of invoking the new expression: new(p) auto(h()); ensures the obligate condition for guaranteed elision.
While extending some pre-existing code, I ran into a situation involving a few nested classes and move construction that produced very unexpected behavior. I was eventually able to produce two possible fixes, but I'm not confident I fully understand the problem to begin with.
Here's a somewhat minimal example, in which a class Foo contains a field of type SubFoo and a unique pointer, and has different copy- and move-constructors to reflect ownership of the unique pointer. Note that there are three macros which are undefined --- corresponding to the original, working state of the code (i.e. none of the asserts fail).
#include <iostream>
#include <unordered_map>
#include <memory>
#include <vector>
#include <cassert>
//#define ADDMAP
//#define SUBFOO_MOVE
//#define FOO_MOVE_NONDEFAULT
class SubFoo {
public:
SubFoo() {}
SubFoo(const SubFoo& rhs) = default;
#ifdef SUBFOO_MOVE
SubFoo(SubFoo&& rhs) noexcept = default;
#endif
private:
#ifdef ADDMAP
std::unordered_map<uint32_t,uint32_t> _map;
#endif
};
class Foo {
public:
Foo(const std::string& name, uint32_t data)
: _name(name),
_data(std::make_unique<uint32_t>(std::move(data))),
_sub()
{
}
Foo(const Foo& rhs)
: _name(rhs._name),
_data(nullptr),
_sub(rhs._sub)
{
std::cout << "\tCopying object " << rhs._name << std::endl;
}
#ifdef FOO_MOVE_NONDEFAULT
Foo(Foo&& rhs) noexcept
: _name(std::move(rhs._name)),
_data(std::move(rhs._data)),
_sub(std::move(rhs._sub))
{
std::cout << "\tMoving object " << rhs._name << std::endl;
}
#else
Foo(Foo&& rhs) noexcept = default;
#endif
std::string _name;
std::unique_ptr<uint32_t> _data;
SubFoo _sub;
};
using namespace std;
int main(int,char**) {
std::vector<Foo> vec;
/* Add elements to vector so that it has to resize/reallocate */
cout << "ADDING PHASE" << endl;
for (uint i = 0; i < 10; ++i) {
std::cout << "Adding object " << i << std::endl;
vec.emplace_back(std::to_string(i),i);
}
cout << endl;
cout << "CHECKING DATA..." << endl;
for (uint i = 0; i < vec.size(); ++i) {
const Foo& f = vec[i];
assert(!(f._data.get() == nullptr || *f._data != i));
}
}
As mentioned above this is the working state of the code: as elements are added into the vector and it must be reallocated memory, the default move constructor is called rather than the copy constructor, as evidenced by the fact that "Copying object #" is never printed and the unique pointer fields remain valid.
However, after adding an unordered map field to SubFoo (which in my case wasn't completely empty, but only contained more basic types), the move constructor is no longer used when resizing/reallocating the vector. Here is a coliru link where you can run this code, which has the ADDMAP macro enabled and results in failed assertions because the copy constructor is called during vector resize and the unique pointers become invalid.
I eventually found two solutions:
Adding a default move constructor for SubFoo
Using a non-default move constructor for Foo that looks exactly like what I would have imagined the default move constructor did.
You can try these out in coliru by uncommenting either of the
SUBFOO_MOVE or FOO_MOVE_NONDEFAULT macros.
However, although I have some rough guesses (see postscripts), I mostly confused and don't really understand why the code was broken in the first place, nor why either of the fixes fixed it. Could someone provide a good explanation of what's going on here?
P.S. One thing I wonder, though I might be off track, is that if the presence of the unordered map in SubFoo somehow made move construction of Foo inviable, why doesn't the compiler warn that the = default move constructor is impossible?
P.P.S. Additionally, while in code shown here I've used "noexcept" move constructors wherever possible, I've had some compiler disagreement about whether this is possible. For example, clang warned me that for Foo(Foo&& rhs) noexcept = default, "error: exception specification of explicitly defaulted move constructor does not match the calculated one". Is this related to the above? Perhaps the move constructor used in vector resizing must be noexcept, and somehow mine wasn't really...
EDIT REGARDING NOEXCEPT
There's likely some compiler dependence here, but for the version of g++ used by coliru, the (default) move constructor for SubFoo does not need to have noexcept specified in order to fix the vector resizing issue (which is not the same thing as specifying noexcept(false), which does not work):
non-noexcept SubFoo move ctor works
while the custom move constructor for Foo must be noexcept to fix things:
non-noexcept Foo move ctor does not work
There is a standard defect (in my opinion) that unordered map's move ctor is not noexcept.
So the defaulted move ctor being noexcept(false) or deleted by your attempted default noexcept(true) seems plausible.
Vector resizing requires a noexecept(true) move ctor, because it cannot sanely and efficiently recover from the 372nd element's move throwing; it can neither roll back nor keep going. It would have to stop with a bunch of elements missing somehow.
Considering
struct C {
C() { printf("C::C()\n" ); }
C(int) { printf("C::C(int)\n" ); }
C( const C& ) { printf("copy-constructed\n"); }
};
And a template function
template< typename T > void foo(){
// default-construct a temporary variable of type T
// this is what the question is about.
T t1; // will be uninitialized for e.g. int, float, ...
T t2 = T(); // will call default constructor, then copy constructor... :(
T t3(); // deception: this is a local function declaration :(
}
int main(){
foo<int>();
foo<C >();
}
Looking at t1, it will not be initialized when T is e.g. int. On the other hand, t2 will be copy-constructed from a default constructed temporary.
The question: is it possible in C++ to default-construct a generic variable, other than with template-fu?
Here's a trick you can use, using a local class:
template <typename T> void foo() {
struct THolder {
T obj;
THolder() : obj() { } // value-initialize obj
};
THolder t1; // t1.obj is value-initialized
}
I think I read about this trick from an answer to another Stack Overflow question, but I am unable to find that question at the moment.
Alternatively, you can use the boost::value_initialized<T> class template, which basically does the same thing, with greater flexibility and consistency and with workarounds for buggy compilers.
In C++0x, it's much easier: you can use an empty initializer list:
T obj{}; // obj is value-initialized
(To the best of my knowledge, only gcc 4.5+ supports C++0x initializer lists. Clang and Visual C++ don't yet support them.)
If you don’t care for the fact that the copy constructor must exist, and just want to prevent it being called:
Don’t worry: it won’t be. The copy constructor call will be elided in this situation. Always, and reliably – even when you compile with optimizations disabled (-O0).
What is your real question? The default constructor is called for t1 instance.
T t2 = T(); // will call default constructor, then copy constructor... :(
Not on my compiler (VC2008). Output for me is...
C::C()
C::C()
Which is what I'd expect it to do. Am I missing something?