I am trying to wrap my head around a weird characteristic of implicitely-declared copy constructors. Take the following example. Once the user implements a custom destructor, the copy-constructor isn't trivial anymore, but is still generated.
#include <type_traits>
#include <cstdio>
struct test {
test() = default;
~test() {
}
test(const test&) = default;
int i{42};
};
static_assert(std::is_copy_constructible_v<test>, "werks"); // OK
static_assert(std::is_trivially_copy_constructible_v<test>, "sad"); // FAILS
int main() {
test t;
test t2(t);
printf("%d\n", t2.i);
return 0;
}
Online version : https://godbolt.org/z/t-8_W3
My understanding of trivial constructors is they are generated by the compiler. However, cppreference states :
The generation of the implicitly-defined copy constructor is
deprecated if T has a user-defined destructor or user-defined copy
assignment operator.
So, it seems there is one more state an implicitly-declared constructor can be in, which is "deprecated". It is not "trivial" and not user implemented, but still generated by your compiler... Is that correct? Does someone know a type trait or workaround to verify if a constructor is "deprecated"?
is_trivially_copy_constructible<T> also requires that T be trivially destructible. What this trait checks is if the hypothetical variable definition:
T t(declval<T const&>());
would invoke no non-trivial operations. But test is not trivially destructible, and that destruction is implicit in that construction.
If you change ~test() { } to ~test() = default, the assertion will no longer trigger.
The note about deprecation of implicitly defined constructor is irrelevant, as you have an explicitly defaulted copy constructor.
Related
This question already has an answer here:
When are C++ default constructors implicitly deleted?
(1 answer)
Closed last month.
As per cppreference doc:
The generation of the implicitly-defined copy constructor is deprecated if T has a user-defined destructor or user-defined copy assignment operator.
But i am not getting any error like default constructor not present.Please correct me if i am wrong.
#include <iostream>
class Demo
{
int val = 100;
public:
~Demo()
{
std::cout << "destructor called\n";
}
Demo& operator= (Demo obj)
{
std::cout << "copy assignment operator called\n";
}
};
int main()
{
Demo obj1;//I am expecting error here : no default constructor present as it is deprecated
Demo obj2(obj1);
return 0;
}
//output:
destructor called
destructor called
The quote talks about the implicitly-defined copy constructor, i.e. the implicit constructor of the form
Demo(const Demo&);
not the implicitly-defined default constructor, i.e. the constructor of the form
Demo();
The latter is what you are using in Demo obj1; and the quote doesn't apply to it.
The line Demo obj2(obj1); should however generate a deprecation warning, because it does use the implicitly-declared copy constructor, which is deprecated since C++11.
However, the compilers seem to have decided to not warn about this deprecation in general. For example GCC requires -Wextra to emit it. Also, the deprecation has been around for a long time, so it might be questionable whether it will ever actually be removed. Nonetheless, a class declaring a copy assignment and/or destructor explicitly is almost always broken if it doesn't also define a copy constructor explicitly.
The following code behaves differently with or without user-defined copy constructor under GCC 8.0.1:
#include <cassert>
struct S {
int i;
int *p;
S() : i(0), p(&i) {}
// S(const S &s) : i(s.i), p(&i) {} // #1
// S(const S &s) : i(s.i), p(s.p) {} // #2
// S(const S &s) = delete; // #3
};
S make_S() {return S{};}
int main()
{
S s = make_S();
assert(s.p == &s.i);
}
With either of the commented user-defined copy constructors (even with #2, the one performing a simple shallow copy), the assertion will not fail, which means guaranteed copy elision works as expected.
However, without any user-defined copy constructor, the assertion fails, which means the object s in main function is not default-constructed. Why does this happen? Doesn't guaranteed copy elision perform here?
Quoting from C++17 Working Draft §15.2 Temporary Objects Paragraph 3 (https://timsong-cpp.github.io/cppwp/class.temporary#3):
When an object of class type X is passed to or returned from a function, if each copy constructor, move constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move constructor, implementations are permitted to create a temporary object to hold the function parameter or result object. ... [ Note: This latitude is granted to allow objects of class type to be passed to or returned from functions in registers. — end note]
In your case, when I made both copy and move constructors defaulted:
S(const S &) = default;
S(S &&) = default;
assertion failed as well with GCC and Clang. Note that implicitly-defined constructors are trivial.
The following code behaves differently with or without user-defined copy constructor under GCC 8.0.1:
#include <cassert>
struct S {
int i;
int *p;
S() : i(0), p(&i) {}
// S(const S &s) : i(s.i), p(&i) {} // #1
// S(const S &s) : i(s.i), p(s.p) {} // #2
// S(const S &s) = delete; // #3
};
S make_S() {return S{};}
int main()
{
S s = make_S();
assert(s.p == &s.i);
}
With either of the commented user-defined copy constructors (even with #2, the one performing a simple shallow copy), the assertion will not fail, which means guaranteed copy elision works as expected.
However, without any user-defined copy constructor, the assertion fails, which means the object s in main function is not default-constructed. Why does this happen? Doesn't guaranteed copy elision perform here?
Quoting from C++17 Working Draft §15.2 Temporary Objects Paragraph 3 (https://timsong-cpp.github.io/cppwp/class.temporary#3):
When an object of class type X is passed to or returned from a function, if each copy constructor, move constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move constructor, implementations are permitted to create a temporary object to hold the function parameter or result object. ... [ Note: This latitude is granted to allow objects of class type to be passed to or returned from functions in registers. — end note]
In your case, when I made both copy and move constructors defaulted:
S(const S &) = default;
S(S &&) = default;
assertion failed as well with GCC and Clang. Note that implicitly-defined constructors are trivial.
For some reason my temporary local object is always copy-constructed/destroyed when added to a vector which is causing problems due to nested std::reference_wrapper which are getting invalid because of the copy-construction and destruction afterwards (std::reference_wrapper targets are inside the object which gets destroyed -> so they are invalid in the copy-constructed object, if the source object is destroyed). But if possible I want to avoid the additional copying / destroying completely - that seems impossible, because whatever I tried always it wants to invoke the copy constructor (even using std::vector::emplace_back).
Considering this simple example (for easier understanding without std::reference_wrapper involved), it always tries to invoke the copy-constructor - I don't get why.
#include <vector>
class A{
public:
A(int a) : a(a){ }
int getInt() const{ return a; }
A(const A&) = delete; /* to deny copy-construction */
private:
int a;
};
int main(int argc, char* argv[]){
std::vector<A> vec;
vec.emplace_back(3); /* tries to call copy constructor */
vec.push_back(A(3)); /* tries to call copy constructor */
vec.push_back(std::move(A(3))); /* tries to call copy constructor */
return 0;
}
Any ideas what I'm missing here?
Per the Visual Studio 2013 documentation, emphasis mine:
"Rvalue references v3.0" adds new rules to automatically generate move constructors and move assignment operators under certain conditions. However, this is not implemented in Visual C++ in Visual Studio 2013, due to time and resource constraints.
Visual Studio 2013 is specified as using Rvalue references v2.1.
Note: As T.C. notes in the comments there is also an issue with explicitly disabling the copy constructor in your example. Per cppreference.com.
If no user-defined move constructors are provided for a class type (struct, class, or union), and all of the following is true:
there are no user-declared copy constructors
there are no user-declared copy assignment operators
there are no user-declared move assignment operators
there are no user-declared destructors
(until C++14) the implicitly-declared move constructor is not defined as deleted due to conditions detailed in the next section
then the compiler will declare a move constructor as an inline public member of its class with the signature T::T(T&&).
A class can have multiple move constructors, e.g. both T::T(const T&&) and T::T(T&&). If some user-defined move constructors are present, the user may still force the generation of the implicitly declared move constructor with the keyword default.
Which means your example code also prevents auto generation of the move constructor (i.e., it has a user-declared copy constructor).
You need to explicitly declare your move constructor and/or move assignment operator. The following works for your example.
class A
{
public:
A(int a) : a(a) {}
A(const A&) = delete;
A(A&& other) : a(other.a) {}
int getInt() const { return a; }
private:
int a;
};
int main(int argc, char* argv[])
{
std::vector<A> vec;
vec.emplace_back(3);
vec.push_back(A(3));
vec.push_back(std::move(A(3)));
return 0;
}
In Stroustrup's Tour of C++ he mentions:
If you are explicit about some defaults, other default definitions will not be generated.
However, this doesn't seem to be the case with gcc 4.9.1. Take for example:
#include <iostream>
using namespace std;
struct A
{
int data;
A() = default;
A(const A&) = default;
};
int main()
{
A a;
a.data = 10;
A b(a);
A c;
c = a;
cout<<b.data<<endl;
cout<<c.data<<endl;
return 0;
}
It compiles and gives the expected output. i.e. the default assignment operator has been generated.
Did I misunderstand Stroustrup's comment?
Firstly, your code doesn't use the class's assignment operator, so it doesn't demonstrate that it exists. However, adding a use of it
c = a;
will work, so the question still stands.
It's not the case that declaring any special function prevents all other special functions from being generated, which seems to be how you're interpreting the quote. Declaring some will inhibit some others. Not having read the book to put the quote in context, I can't say whether or not it's misleading.
The rules are, roughly:
declaring any constructor will prevent an implicit default constructor;
declaring a move constructor or move-assignment operator will prevent an implicit copy constructor and copy-assignment operator;
declaring a destructor, copy constructor or copy-assignment operator will prevent an implicit move constructor and move-assignment operator;
That's a simplification; there are more nuances and exceptions. Read the language specification if you want the gory details.
Those declarations are currently generated by default (which is why the code you provided works), but this behavior is subject to be removed at any time in further revisions to the C++ standard (deprecated).
In Stroustrup's C++11 FAQ, he states
If any move, copy, or destructor is explicitly specified (declared, defined, =default, or =delete) by the user, no move is generated by default.
That is pretty self explanatory. However, he goes on to say that...
If any move, copy, or destructor is explicitly specified (declared, defined, =default, or =delete) by the user, any undeclared copy operations are generated by default, but this is deprecated, so don't rely on that.
He then lists a few examples:
class X1 {
X1& operator=(const X1&) = delete; // Disallow copying
};
This implicitly also disallows moving of X1s. Copy initialization is allowed, but deprecated.
class X2 {
X2& operator=(const X2&) = delete;
};
This implicitly also disallows moving of X2s. Copy initialization is allowed, but deprecated.
class X3 {
X3& operator=(X3&&) = delete; // Disallow moving
};
This implicitly also disallows copying of X3s.
class X4 {
~X4() = delete; // Disallow destruction
};
This implicitly also disallows moving of X4s. Copying is allowed, but deprecated.