Below is my code:
#include <iostream>
using namespace std;
class Rectangle {
int width, height;
public:
Rectangle(int, int);
int area() { return (width * height); }
};
Rectangle::Rectangle(int a, int b) {
width = a;
height = b;
}
int main() {
Rectangle A(3, 4);
Rectangle B = Rectange(3,4);
return 0;
}
I didn't define any copy constructors or assignment operator for Rectangle
class.
Is it true that Rectangle B = Rectangle(3, 4);
actually does three things in serial?
Allocate memory space for a temporary variable (let's use tmp
to denote it) of Rectangle, call Rectangle::Rectangle(3, 4)
to intialize it.
Allocate memory space for variable B
, initialize it with the default constructor
(memberwise) Copy tmp
to B
with the assignment operator Rectangle& operator = (const Rectangle &)
Does that explanation make sense? I think I might understand wrongly because this process looks very clumsy and inefficient compared with Rectangle A(3, 4);
.
Does anyone have ideas about this? Is it true that Rectangle A(3,4)
is equivalent to Rectangle A = Rectangle(3, 4);
? Thanks!
Is it true that
Rectangle B = Rectangle(3, 4);
actually does three things in serial?
No, that isn't true, but it's not really your fault: this is confusing.
T obj2 = obj1
is not an assignment, but an initialisation .
So you're right that obj1
will be created first (in your case, a temporary), but obj2
will be constructed using the copy constructor , not default constructed then assigned-to.
For
Rectangle C = new Rectangle(3, 4);
, the only difference is that the memory space of the temporary variable is allocated to the heap instead of stack.
No, it won't compile, but Rectangle* C
would. There are plenty of explanations of dynamic allocation here already.
In this case, there's a significant difference between what actually happens, and what theoretically sort of happens.
The first is simple: Rectangle A(3, 4);
just constructs a Rectangle with width
and height
initialized to 3
and 4
. It's all done in "one step" using the Rectangle(int, int);
constructor you defined. Simple and straightforward--so it's what's generally preferred and recommended when it's possible.
Then let's consider: Rectangle B = Rectangle(3,4);
. In theory, this constructs a temporary object, then copy-constructs B
from that temporary.
In practice, what happens is that the compiler checks that it would be able to create the temporary object, and that it would be able to use the copy constructor to initialize B
from that temporary. Having checked that this would be possible, nearly any competent compiler (at least when optimization is enabled, and often even when it's not) will generate code essentially identical to what it did for creating A
.
If, however, you delete the copy constructor, by adding:
Rectangle(Rectangle const &) = delete;
...then the compiler will find that it can't copy construct B
from the temporary, and will refuse to compile the code. Even if the final, generated code never actually uses the copy constructor, it has to be available for this to work.
Finally, let's look at your third example (with the syntax corrected):
Rectangle *C = new Rectangle(3, 4);
Despite looking somewhat like the line above that created B
, the construction involved is really much more like the previous one that you used to create A
. Only one object is (even theoretically) created. It's allocated from the free store and directly initialized using your Rectangle(int, int);
constructor.
Then the address of that object is used to initialize C
(which is only a pointer). Like the initialization of A
, this will work even if Rectangle
's copy constructor is deleted, because even in theory there's no copying of a Rectangle
involved.
None of these involves an assignment operator. If you were to delete the assignment operator:
Rectangle &operator=(Rectangle const &) = delete;
...all this code would still be fine, because (despite the use of the =
) there's no assignment anywhere in the code.
To use an assignment (if you really insisted on doing so) you could (for example) do something like:
Rectangle A(3, 4);
Rectangle B = Rectangle(5, 6);
B = A;
This still uses only constructors to create and initialize A
and B
, but then uses the assignment operator to assign the value of A
to B
. In this case, if you deleted the assignment operator as shown above, the code would fail (would not compile).
Some of our misunderstanding seems to stem from the fact that the compiler with automatically create "special member functions" for you if you don't do something to prevent it from doing so. One of the ways you can prevent it from doing so is the = delete;
syntax shown above, but that's not the only one. For example, if your class includes a member variable of reference type, the compiler won't create an assignment operator for you. If you start with something simple like this:
struct Rectangle {
int width, height;
};
...the compiler will generate a default constructor, copy constructor, move constructor, copy assignment and move assignment operators, all entirely automatically.
Rectangle A(3, 4);
always simply invokes the Rectangle(int, int)
constructor, throughout all of the history of C++ having constructors. Simple. Boring.
Now for the fun part.
In the newest (as of this writing) edition of the standard, Rectangle B = Rectangle(3,4);
immediately collapses down into Rectangle B(3,4);
This happens regardless of what the nature of Rectangle
's move or copy constructors is. This feature is commonly referred to as guaranteed copy elision, although it's important to stress that there is no copy and no move here. What happens is B
is direct-initialized from (3,4)
.
Before C++17, there is a temporary Rectangle
constructed that a compiler may optimize away (and by may, I mean it will certainly, unless you tell it not to). But your ordering of events is not correct. It's important to note that no assignment happens here . We are not assigning to B
. We are constructing B
. Code of the form:
T var = expr;
is copy- initialization , it is not copy-assignment. Hence, we do two things:
Rectangle
using the Rectangle(int, int)
constructor Rectangle
constructor given a prvalue of type Rectangle
) If the move constructor is deleted (or, pre-C++11, the copy constructor is marked private
), then trying to construct B
in this way is ill-formed. If the special member functions are left alone (as they are in this example), the two declarations of A
of B
will certainly compile to the same code.
The form of initialization in B
may look more familiar if you actually drop the type:
auto B = Rectangle(3,4);
This is the so-called AAA (Almost Always Auto) style of declarations that Herb Sutter is a fan of. This does exactly the same thing as Rectangle B = Rectangle(3, 4)
, just with the leading step of first deducing the type of B
as Rectangle
.
Rectangle* C = new Rectangle(3,4);
, there's grammar error in your code. operator =(Rectangle& rho)
is by-default defined returning reference. Thus, Rectangle B = Rectangle(3, 4);
won't create tmp
object as you described above.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.