I've been messing with references wrapped in container classes. Why is the following code legal and appear to behave correctly?
#include <iostream>
class Foo
{
public:
Foo( int i ) : i_( i ) {}
int i_;
};
class FooWrapper
{
public:
FooWrapper( Foo& foo ) : foo_( foo ) {}
Foo& foo_;
};
int main( int argc, char* argv[] )
{
Foo foo( 42 );
FooWrapper fw( foo );
FooWrapper fw2 = fw;
std::cout << fw2.foo_.i_ << std::endl;
return 0;
}
In the absence of an explicit operator=, I believe C++ does a memberwise copy. So when I do FooWrapper fw2 = fw;, is this not two operations: (1) create FooWrapper fw with a default ctor followed by (2) assignment from fw to fw2? I'm sure this isn't happening, because a reference can't be created uninitialized, so is the creation of fw2 actually treated as a copy construction?
I'm sometimes unclear on the roles of copy construction vs. assignment; they seem to bleed into each other sometimes, as in this example, but there's probably some rule I'm unaware of. I'd be grateful for an explanation.
Despite the = in the syntax, FooWrapper fw2 = fw; copy constructs fw2 (using the copy constructor). Neither default construction nor assignment is involved at all.
And to answer the question in the title: no, references cannot be assigned. If you wrote code that attempted to actually default construct or assign a FooWrapper, such as:
FooWrapper fw2;
fw2 = fw;
...this would fail. Officially, only a "diagnostic" is required. Unofficially, every compiler I know of would/will refuse to compile it.
In the line below, you are constructing fw2 by making it a copy of fw. That is, you are invoking the copy-constructor.
FooWrapper fw2 = fw;
Example
Here is an (online) example on how default constructor, copy constructor, copy assignment operator, and move assignment operator (from C++11) are invoked.
#include <iostream>
struct foo
{
foo() {std::cout << "Default constructor" << '\n';}
foo(foo const&) {std::cout << "Copy constructor" << '\n';}
foo& operator=(foo const&) {std::cout << "Copy assignment operator" << '\n'; return *this; }
foo& operator=(foo &&) {std::cout << "Move assignment operator" << '\n'; return *this; }
};
int main( int argc, char* argv[] )
{
foo a; // Default constructor
foo b = a; // Copy constructor
foo c; // Default constructor
c = b; // Copy assignment operator
b = std::move(c); // Move assignment operator
}
This initialization actually calls the copy constructor.
FooWrapper fw2 = fw;
and is equivalent to
FooWrapper fw2(fw);
The implicitly defined copy constructor doesn't need to create an uninitialized reference
For reference:
http://en.cppreference.com/w/cpp/language/copy_constructor
Related
I have this simple piece of code in C++17 and I was expecting that the move constructor was called (or the copy constructor, if I was doing something wrong), while it is just calling the normal constructor and I cannot why is doing this optimization.
I am compiling with -O0 option.
#include <iostream>
using namespace std;
struct Foo {
int m_x;
Foo(int x) : m_x(x) { cout << "Ctor" << endl; }
Foo(const Foo &other) : m_x(other.m_x) { cout << "Copy ctor" << endl; }
Foo(Foo &&other) : m_x(other.m_x) {
other.m_x = 0;
cout << "Move ctor" << endl;
}
};
void drop(Foo &&foo) {}
int main() { drop(Foo(5)); }
I cannot why is doing this optimization.
This is not due to any optimization in C++17. Instead this is due to the fact that you're passing an int when you wrote Foo(5). And since you've provided a converting constructor Foo::Foo(int), it will be used to create a Foo object which will then be bound to the rvalue reference parameter of drop.
Note that in C++17, even if we were to make the parameter of drop to be of type Foo instead of Foo&&, then also there will be no call to the move constructor because of mandatory copy elison.
C++11
On the other hand, if you were using C++11 and using the flag -fno-elide-constructors and parameter to drop was of type Foo instead of Foo&& then you could see that a call would be made to the move ctor.
//--------vvv-----------> parameter is of type Foo instead of Foo&&
void drop(Foo foo) {
std::cout<<"drop called"<<std::endl;
}
int main() {
drop(Foo(5)); //in c++11 with -fno-elide-constructors the move ctor will be called
}
The output of the above modified version in C++11 with -fno-elide-constructors is:
Ctor
Move ctor
drop called
Demo
In function main you create a temporary Foo object from integer 5 but you don't move (nor copy) from it anywhere. To actually call your move (or copy) constructor, you have to move- (or copy-) construct another object from your temporary Foo.
E.g., to call Foo's move constructor:
void drop(Foo &&foo) {
// Move-construct tmp from foo.
Foo tmp { std::move(foo) };
}
From what I learned, I thought Foo a = 1 is equivalent to Foo a = (Foo)1.
With copy constructor declared, yes, they both result in calling Converting constructor.
class Foo
{
public:
// Converting constructor
Foo(int n) : value_(n) { std::cout << "Converting constructor." << std::endl; }
// Copy constructor
Foo(const Foo&) { std::couut << "Copy constructor." << std::endl; }
private:
int value_;
};
int main()
{
Foo a = 1; // OK - prints only "Converting constructor."
Foo b = (Foo)1; // OK - prints only "Converting constructor."
}
In contrast, without copy constructor, it doesn't compile even though it doesn't ever call copy constructor.
class Foo
{
public:
// Converting constructor
Foo(int n) : value_(n) { std::cout << "Converting constructor." << std::endl; }
// Copy constructor deleted
Foo(const Foo&) = delete;
private:
int value_;
};
int main()
{
Foo a = 1; // OK - prints only "Converting constructor."
Foo b = (Foo)1; // Error C2280: 'Foo::Foo(const Foo &)': attempting to reference a deleted function
}
What makes difference?
Thank you.
Foo a = 1; calls Foo(int n). (Foo)1 also calls Foo(int n). Foo b = (Foo)1; calls copy constructor Foo(const Foo&). Until C++17 the last call can be elided but the copy constructor must be available. Since C++17 copy elision is mandatory and the copy constructor isn't necessary: https://en.cppreference.com/w/cpp/language/copy_elision
Probably you're using a C++ standard before C++17 and the copy constructor is required but not called.
"From what I learned, I thought Foo a = 1 is equivalent to Foo a = (Foo)1." That's not equivalent. The first is one constructor call and the second is two constructor calls but one call can/must be elided.
I wrote the following program to test when the copy constructor is called and when the assignment operator is called:
#include
class Test
{
public:
Test() :
iItem (0)
{
std::cout << "This is the default ctor" << std::endl;
}
Test (const Test& t) :
iItem (t.iItem)
{
std::cout << "This is the copy ctor" << std::endl;
}
~Test()
{
std::cout << "This is the dtor" << std::endl;
}
const Test& operator=(const Test& t)
{
iItem = t.iItem;
std::cout << "This is the assignment operator" << std::endl;
return *this;
}
private:
int iItem;
};
int main()
{
{
Test t1;
Test t2 = t1;
}
{
Test t1;
Test t2 (t1);
}
{
Test t1;
Test t2;
t2 = t1;
}
}
This results in the following output (just added empy lines to make it more understandable):
doronw#DW01:~$ ./test
This is the default ctor
This is the copy ctor
This is the dtor
This is the dtor
This is the default ctor
This is the copy ctor
This is the dtor
This is the dtor
This is the default ctor
This is the default ctor
This is the assignment operator
This is the dtor
This is the dtor
The second and third set behave as expected, but in the first set the copy constructor is called even though the assignment operator is used.
Is this behaviour part of the C++ standard or just a clever compiler optimization (I am using gcc 4.4.1)
No assignment operator is used in the first test-case. It just uses the initialization form called "copy initialization". Copy initialization does not consider explicit constructors when initializing the object.
struct A {
A();
// explicit copy constructor
explicit A(A const&);
// explicit constructor
explicit A(int);
// non-explicit "converting" constructor
A(char const*c);
};
A a;
A b = a; // fail
A b1(a); // succeeds, "direct initialization"
A c = 1; // fail, no converting constructor found
A d(1); // succeeds
A e = "hello"; // succeeds, converting constructor used
Copy initialization is used in those cases that correspond to implicit conversions, where one does not explicitly kick off a conversion, as in function argument passing, and returning from a function.
C++ standard 8.5/12
The initialization that occurs in
argument passing, function return,
throwing an exception (15.1), handling
an exception (15.3), and
brace-enclosed initializer lists
(8.5.1) is called copy-initialization
and is equivalent to the form
T x = a;
The initialization that occurs in new
expressions (5.3.4), static_cast
expressions (5.2.9), functional
notation type conversions (5.2.3), and
base and member initializers (12.6.2)
is called direct-initialization and is
equivalent to the form
T x(a);
Your first set is according to the C++ standard, and not due to some optimization.
Section 12.8 ([class.copy]) of the C++ standard gives a similar example:
class X {
// ...
public:
X(int);
X(const X&, int = 1);
};
X a(1); // calls X(int);
X b(a, 0); // calls X(const X&, int);
X c = b; // calls X(const X&, int);
The last line would be the one matching your case.
I want to enforce explicit conversion between structs kind of like native types:
int i1;
i1 = some_float; // this generates a warning
i1 = int(some_float): // this is OK
int i3 = some_float; // this generates a warning
I thought to use an assignment operator and copy constructor to do the same thing, but the behavior is different:
Struct s1;
s1 = other_struct; // this calls the assignment operator which generates my warning
s1 = Struct(other_struct) // this calls the copy constructor to generate a new Struct and then passes that new instance to s1's assignment operator
Struct s3 = other_struct; // this calls the COPY CONSTRUCTOR and succeeds with no warning
Are there any tricks to get that third case Struct s3 = other_struct; construct s3 with the default constructor and then call the assignment operator?
This all compiles and runs as it should. The default behavior of C++ is to call the copy constructor instead of the assignment operator when you create a new instance and call the copy constructor at once, (i.e. MyStruct s = other_struct;becomes MyStruct s(other_struct); not MyStruct s; s = other_struct;. I'm just wondering if there are any tricks to get around that.
EDIT: The "explicit" keyword is just what I needed!
class foo {
foo(const foo& f) { ... }
explicit foo(const bar& b) { ... }
foo& operator =(const foo& f) { ... }
};
foo f;
bar b;
foo f2 = f; // this works
foo f3 = b; // this doesn't, thanks to the explicit keyword!
foo f4 = foo(b); // this works - you're forced to do an "explicit conversion"
Disclaimer: I'm ready to take the downvotes on this, since this doesn't answer the question. But this could be useful to the OP.
I think it is a very bad idea to think of the copy constructor as default construction + assignment. It is the other way around:
struct some_struct
{
some_struct(); // If you want a default constructor, fine
some_struct(some_struct const&); // Implement it in the most natural way
some_struct(foo const&); // Implement it in the most natural way
void swap(some_struct&) throw(); // Implement it in the most efficient way
// Google "copy and swap idiom" for this one
some_struct& operator=(some_struct x) { x.swap(*this); return *this; }
// Same idea
some_struct& operator=(foo const& x)
{
some_struct tmp(x);
tmp.swap(*this);
return *this;
}
};
Implementing things that way is fool proof, and is the best you can obtain in term of conversion semantics in C++, so it is the way to go here.
You can get around this if you overload the type cast operator for other_struct, and edit the original structure accordingly. That said, it's extremely messy and there generally isn't a good reason to do so.
#include <iostream>
using namespace std;
struct bar;
struct foo {
explicit foo() {
cout << "In foo default constructor." << endl;
}
explicit foo(bar const &) {
cout << "In foo 'bar' contructor." << endl;
}
foo(foo const &) {
cout << "In foo constructor." << endl;
}
foo const & operator=(bar const &) {
cout << "In foo = operator." << endl;
return *this;
}
};
struct bar {
operator foo() {
cout << "In bar cast overload." << endl;
foo x;
x = *this;
return x;
}
};
int main() {
bar b;
foo f = b;
return 0;
}
Outputs:
In bar cast overload.
In foo default constructor.
In foo = operator.
In foo constructor.
In foo constructor.
In short, no.
The long version...actually that's about it. That's just not how it works. Had to come up with something to fill the character requirement though.
I don't think so. When you write
Struct s3 = other_struct;
It looks like an assignment, but really it's just declarative syntax that calls a constructor.
I wrote the following program to test when the copy constructor is called and when the assignment operator is called:
#include
class Test
{
public:
Test() :
iItem (0)
{
std::cout << "This is the default ctor" << std::endl;
}
Test (const Test& t) :
iItem (t.iItem)
{
std::cout << "This is the copy ctor" << std::endl;
}
~Test()
{
std::cout << "This is the dtor" << std::endl;
}
const Test& operator=(const Test& t)
{
iItem = t.iItem;
std::cout << "This is the assignment operator" << std::endl;
return *this;
}
private:
int iItem;
};
int main()
{
{
Test t1;
Test t2 = t1;
}
{
Test t1;
Test t2 (t1);
}
{
Test t1;
Test t2;
t2 = t1;
}
}
This results in the following output (just added empy lines to make it more understandable):
doronw#DW01:~$ ./test
This is the default ctor
This is the copy ctor
This is the dtor
This is the dtor
This is the default ctor
This is the copy ctor
This is the dtor
This is the dtor
This is the default ctor
This is the default ctor
This is the assignment operator
This is the dtor
This is the dtor
The second and third set behave as expected, but in the first set the copy constructor is called even though the assignment operator is used.
Is this behaviour part of the C++ standard or just a clever compiler optimization (I am using gcc 4.4.1)
No assignment operator is used in the first test-case. It just uses the initialization form called "copy initialization". Copy initialization does not consider explicit constructors when initializing the object.
struct A {
A();
// explicit copy constructor
explicit A(A const&);
// explicit constructor
explicit A(int);
// non-explicit "converting" constructor
A(char const*c);
};
A a;
A b = a; // fail
A b1(a); // succeeds, "direct initialization"
A c = 1; // fail, no converting constructor found
A d(1); // succeeds
A e = "hello"; // succeeds, converting constructor used
Copy initialization is used in those cases that correspond to implicit conversions, where one does not explicitly kick off a conversion, as in function argument passing, and returning from a function.
C++ standard 8.5/12
The initialization that occurs in
argument passing, function return,
throwing an exception (15.1), handling
an exception (15.3), and
brace-enclosed initializer lists
(8.5.1) is called copy-initialization
and is equivalent to the form
T x = a;
The initialization that occurs in new
expressions (5.3.4), static_cast
expressions (5.2.9), functional
notation type conversions (5.2.3), and
base and member initializers (12.6.2)
is called direct-initialization and is
equivalent to the form
T x(a);
Your first set is according to the C++ standard, and not due to some optimization.
Section 12.8 ([class.copy]) of the C++ standard gives a similar example:
class X {
// ...
public:
X(int);
X(const X&, int = 1);
};
X a(1); // calls X(int);
X b(a, 0); // calls X(const X&, int);
X c = b; // calls X(const X&, int);
The last line would be the one matching your case.