I'm trying to understand section 4.6.2 of the book where move constructor and move assignment are introduced.
The way I understand it, the goal is to avoid a stack allocated vector to be needlessly copied (I'm speculating... on the heap or directly the call stack of the caller) before returning it to the caller. The code looks like so:
Vector operator+(const Vector& a, const Vector& b) {
if (a.size() != b.size())
throw length_error {"Vectors must be the same size"};
Vector res(a.size());
for (int i = 0; i < res.size(); i++)
res[i] = a[i]+b[i];
return res;
}
Here's my usage:
int main() {
Vector v1 {8,8,9};
Vector v3 = v1+v1+v1+v1;
cout << v3 << endl;
}
I have traces in my destructors and I would expect, without the usage of moving constructor/assignments, there to be additional destruction of the res local in operator+.
However there isn't, there is only one destruction that occurs on the subresult v1+v1 at the end of the program (this destruction has to occur since v1+v1 exists at a point in the program), one on the subresult of the former +v1, and then another one for the last +v1.
Also, implementing the moving constructor/assignment reveals that those are never called.
Why do I not see two destruction for each of the subresult without the moving constructor and why are my moving constructor not called when implemented?
I would wager that this is the result of copy elision. In short, the compiler is allowed to omit calling the copy/move constructors, even if they have side effects. So you cannot count on them being called
Relevant part from the reference says
Under the following circumstances, the compilers are required to omit
the copy and move construction of class objects, even if the copy/move
constructor and the destructor have observable side-effects. The
objects are constructed directly into the storage where they would
otherwise be copied/moved to.
Copy elision is an exception to the as-if rule, where compilers are only allowed to do optimizations that preserve the old behavior.
Related
I frequently need to implement C++ wrappers for "raw" resource handles, like file handles, Win32 OS handles and similar. When doing this, I also need to implement move operators, since the default compiler-generated ones will not clear the moved-from object, yielding double-delete problems.
When implementing the move assignment operator, I prefer to call the destructor explicitly and in-place recreate the object with placement new. That way, I avoid duplication of the destructor logic. In addition, I often implement copy assignment in terms of copy+move (when relevant). This leads to the following code:
/** Canonical move-assignment operator.
Assumes no const or reference members. */
TYPE& operator = (TYPE && other) noexcept {
if (&other == this)
return *this; // self-assign
static_assert(std::is_final<TYPE>::value, "class must be final");
static_assert(noexcept(this->~TYPE()), "dtor must be noexcept");
this->~TYPE();
static_assert(noexcept(TYPE(std::move(other))), "move-ctor must be noexcept");
new(this) TYPE(std::move(other));
return *this;
}
/** Canonical copy-assignment operator. */
TYPE& operator = (const TYPE& other) {
if (&other == this)
return *this; // self-assign
TYPE copy(other); // may throw
static_assert(noexcept(operator = (std::move(copy))), "move-assignment must be noexcept");
operator = (std::move(copy));
return *this;
}
It strikes me as odd, but I have not seen any recommendations online for implementing the move+copy assignment operators in this "canonical" way. Instead, most websites tend to implement the assignment operators in a type-specific way that must be manually kept in sync with the constructors & destructor when maintaining the class.
Are there any arguments (besides performance) against implementing the move & copy assignment operators in this type-independent "canonical" way?
UPDATE 2019-10-08 based on UB comments:
I've read through http://eel.is/c++draft/basic.life#8 that seem to cover the case in question. Extract:
If, after the lifetime of an object has ended ..., a new object is
created at the storage location which the original object occupied, a
pointer that pointed to the original object, a reference that referred
to the original object, ... will
automatically refer to the new object and, ..., can be used to manipulate the new object, if ...
There's some obvious conditions thereafter related to the same type and const/reference members, but they seem to be required for any assignment operator implementation.
Please correct me if I'm wrong, but this seems to me like my "canonical" sample is well behaved and not UB(?)
UPDATE 2019-10-10 based on copy-and-swap comments:
The assignment implementations can be merged into a single method that takes a value argument instead of reference. This also seem to eliminate the need for the static_assert and self-assignment checks. My new proposed implementation then becomes:
/** Canonical copy/move-assignment operator.
Assumes no const or reference members. */
TYPE& operator = (TYPE other) noexcept {
static_assert(!std::has_virtual_destructor<TYPE>::value, "dtor cannot be virtual");
this->~TYPE();
new(this) TYPE(std::move(other));
return *this;
}
There is a strong argument against your "canonical" implementation — it is wrong.
You end the lifetime the original object and create a new object in its place. However, pointers, references, etc. to the original object are not automatically updated to point to the new object — you have to use std::launder. (This sentence is wrong for most classes; see Davis Herring’s comment.) Then, the destructor is automatically called on the original object, triggering undefined behavior.
Reference: (emphasis mine) [class.dtor]/16
Once a destructor is invoked for an object, the object no longer
exists; the behavior is undefined if the destructor is invoked for an
object whose lifetime has ended. [ Example: If the destructor
for an automatic object is explicitly invoked, and the block is
subsequently left in a manner that would ordinarily invoke implicit
destruction of the object, the behavior is undefined.
— end example ]
[basic.life]/1
[...] The lifetime of an object o of type T ends when:
if T is a class type with a non-trivial destructor ([class.dtor]), the destructor call starts, or
the storage which the object occupies is released, or is reused by an object that is not nested within o ([intro.object]).
(Depending on whether the destructor of your class is trivial, the line of code that ends the lifetime of the object is different. If the destructor is non-trivial, explicitly calling the destructor ends the lifetime of the object; otherwise, the placement new reuses the storage of the current object, ending its lifetime. In either case, the lifetime of the object has been ended when the assignment operator returns.)
You may think that this is yet another "any sane implementation will do the right thing" kind of undefined behavior, but actually many compiler optimization involve caching values, which take advantage of this specification. Therefore, your code can break at any time when the code is compiled under a different optimization level, by a different compiler, with a different version of the same compiler, or when the compiler just had a terrible day and is in a bad mood.
The actual "canonical" way is to use the copy-and-swap idiom:
// copy constructor is implemented normally
C::C(const C& other)
: // ...
{
// ...
}
// move constructor = default construct + swap
C::C(C&& other) noexcept
: C{}
{
swap(*this, other);
}
// assignment operator = (copy +) swap
C& C::operator=(C other) noexcept // C is taken by value to handle both copy and move
{
swap(*this, other);
return *this;
}
Note that, here, you need to provide a custom swap function instead of using std::swap, as mentioned by Howard Hinnant:
friend void swap(C& lhs, C& rhs) noexcept
{
// swap the members
}
If used properly, copy-and-swap incurs no overhead if the relevant functions are properly inlined (which should be pretty trivial). This idiom is very commonly used, and an average C++ programmer should have little trouble understanding it. Instead of being afraid that it will cause confusion, just take 2 minutes to learn it and then use it.
This time, we are swapping the values of the objects, and the lifetime of the object is not affected. The object is still the original object, just with a different value, not a brand new object. Think of it this way: you want to stop a kid from bullying others. Swapping values is like civilly educating them, whereas "destroying + constructing" is like killing them making them temporarily dead and giving them a brand new brain (possibly with the help of magic). The latter method can have some undesirable side effects, to say the least.
Like any other idiom, use it when appropriate — don't just use it for the sake of using it.
I believe the example in http://eel.is/c++draft/basic.life#8 clearly proves that assignment operators can be implemented through inplace "destroy + construct" assuming certain limitations related to non-const, non-overlapping objects and more.
I have a function:
std::string makeMeat() { return "Pork"; }
And somewhere in code I use it this way:
std::string meat = makeMeat();
I want to know what is exact sequence of operations made on this line of code. Assuming two different circumstances:
std::string has no move constructor (just for example)
std::string has move constructor
I guess makeMeat() creates temporary object of class std::string.
std::string temp("Pork");
After that std::string meat object is created and initialized with copy constructor by data from temp object?
std::string meat(temp);
Finally temp object is destroyed?
I think it happens this way if there was no return value optimization.
What happens if it was?
The string is directly constructed in meat. No temporaries with a distinct lifetime exist. This is known as elision.
This behaviour is mandated under C++17 and in practice happens in any reasonably modern production-quality modern compiler with no pathological build flags set in C++03 11 and 14.
In C++14 and earlier, the class must have a move or copy ctor for the above to happen, or you get a build break. No code in said constructors will run.
Ancient or toy compilers, or compilers with pathological flags telling them not to elide, may make up to 2 temporary objects and mess around with copies. This case isn't interesting, as pathological compiler states are equally free to implement a+=b; (with a and b integral types) as for (i from 0 to b)++a;! You should honestly consider lack of elision as equally pathological.
Elision in C++ refers to the standard-permitted merging of object lifetime and identity. So in some sense 3 strings (the temporary eithin the function, the return value, and the value constructed from the return value) exist, their identities are merged into one object with a unified lifetime.
You can test this using a custom structure:
struct S {
S (const char *);
S (S const&) = default;
S (S&&) = default;
virtual ~S();
};
S get_s () { return "S"; }
int main () {
S s = get_s();
}
Without option, g++ will elide most constructors call and this code is equivalent to:
S s("S");
So only the constructor from const char * is called.
Now, if you tell g++ to not elide constructor (-fno-elide-constructors), there are three constructors/destructors call:
The first one create a temporary S("S");
The second one create a temporary inside get_s, S(S&&);
Then the destructor of the first temporary is called;
Then the move constructor is called inside main;
Then the destructor of the temporary returned by get_s is called;
Then the destructor of s is called.
If S does not have a move constructor, you can simply replace move constructors by copy constructors in the above list.
If I have a function like the following:
stack fillStack(){
stack a;
return a;
}
void main(){
stack s=fillStack();
}
Consider we have a class called stack.
How many constructor and destructor will be called?
Here is what should be happening:
stack fillStack() {
stack a; // constructor
return a; // copy constructor and destructor a
}
int main(){
stack s=fillStack(); // copy constructor and destructor of the temporary
} // destructor s
In practice the standard explicitly allows the copy-constructors to be
optimized away (this is called copy elision) and the value to be
constructed in place. That could end up looking something like this:
void fillStack(stack&);
int main() {
stack s;
fillStack(s); // with reference
}
Although, copy-construction must still be well-formed even if the
compiler applies this transformation. If copy-construction can have
side-effects this optimization can lead to somewhat odd behavior (try
printing something from the copy-constructor and observe the behavior
on different optimization levels).
This optimization becomes largely unnecessary with C++11 due to
move-semantics.
Assuming NO compiler optimization, it should be 2 Copy constructor calls - One from function local to return value temporary, one from return value temporary to s
See this code here:
class test
{
int n;
int *j;
public:
test(int m)
{
n = 12;
j = new int;
cin >> *j;
}
void show()
{
cout << n << ' ' << *j << endl;
}
~test()
{
delete j;
}
};
int main()
{
test var = 123;
var.show();
return 0;
}
In this program compiler should complain about double deleting of j. First delete is done when temporary object temporary(123) is destroyed. The second delete is done when var object is destroyed. But this is working fine?
Does it mean temporary object does not call destructor?
The contentious line is this:
test var = 123;
The relevant standard text (that the pundits in the comments are referencing), I believe, is (8.5, "Declarators"):
The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type. The temporary is an rvalue. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized;
Indeed, in 12.6, we get an example of this:
complex f = 3; // construct complex(3) using
// complex(double)
// copy it into f
Thus, in your use of =, your implementation is probably directly constructing object and eliminating the intermediate temporary entirely (and, as the comments have noted, most do).
This class doesn't copy properly, so creating a copy of it (and the freeing the copy and the original) would result in a double delete (and crashes, undefined behavior, etc.). Because no copies are created, this scenario does not happen above.
Two points: first, in this particular case, the compiler is allowed to
optimize out the temporary, by an explicit authorization of the
standard. All of the compilers I'm familiar with do. You can verify
whether this is happening in your code by defining a copy constructor
and instrumenting it.
Second is that if the temporary isn't optimized out, your code has
undefined behavior. A double delete can have any imaginable behavior:
an immediate crash, corruption of the free space arena (leading to a
much later crash if the program continues running), no effect what so
ever, or anything else. The fact that you are not seeing any symptoms
doesn't mean that the code is correct.
The fact that the code happens to not blow up does not mean that it is correct.
Your class is buggy in that it is susceptible to double deletes in the manner you describe.
For example, changing var.show(); to the following:
test(var).show();
makes the code reliably blow up on my computer.
To fix, implement the copy constructor and the assignment operator.
We have shallow copy and deep copy who can do the job for us when we want to copy objects in C++. So,
What is Lazy copy?
Is this a thing that is taken care by a programmer or compiler does this on its own?
What are the programming scenarios where a lazy copy is advantageous?
What is Lazy Copy?
Wikipedia Defines this aptly.
A lazy copy is a combination of both shallow copy and Deep Copy. When initially copying an object, a (fast) shallow copy is used. A counter is also used to track how many objects share the data. When the program wants to modify an object, it can determine if the data is shared (by examining the counter) and can do a deep copy if necessary.
Lazy copy looks to the outside just as a deep copy but takes advantage of the speed of a shallow copy whenever possible. The downside are rather high but constant base costs because of the counter. Also, in certain situations, circular references can also cause problems.
Is this a thing that is taken care by a programmer or something that the compiler does on its own?
The programmer has to implement this behavior for his own classes.
A compiler performs shallow copies in copying functions(copy constructor & assignment operator) by default.
Deep Copy is what a programmer has to implement for his class, so that the special handling of members(pointers) can be in place for copying functions.
What are the programming scenarios where a lazy copy is advantageous?
Ideally,
A situation wherein copying an object causes a performance penalty but the objects are not being modified very frequently the Lazy copy would be advantageous in terms of performance.
The Wikipedia cites a number of examples where, Lazy Copy(Copy On Write) is used.
Lazy copy is roughly:
perform shallow copy right away
but perform the deep copy later, only when it becomes absolutely necessary (i.e. when object is about to be modified), in hope this moment will never come.
So they are different concepts. Lazy copy is essentially a run-time optimization while shallow/deep copy is a compile-time construct that can be used to implement the lazy copy but can be used independently as well.
I'm not sure what you mean by "having shallow and deep copy who can do the job for us". Whe you write your copy constructor and assignment operator, you need to make the decision, on how you want to make a copy.
In shallow copy, you just copy the object members directly. If those members are pointers to heap memory, the copy points to the same block of heap memory. This is the default copy performed by C++ if you don't provide a copy constructor.
In a deep copy, any memory pointed to by member pointers is itself copied (potentially recursing on the members of those objects as well).
In a lazy copy, you start out with a shallow copy. If the object or its children are never modified, then you're fine. No need to make a second copy of the heap memory. When either of the two copies gets modified, a deep copy is first performed so that the modifications don't get applied to both objects. This is also known as coopy-on-write.
The purpose of lazy copying is to get the appearance of deep-copies, with some of the performance benefits of shallow copying. Whether this ends up being a performance gain depends on the usage of the objects. If you plan on having many copies of an object unmodified, then you're likely to see the advantages. If most objects are getting modified eventually anyway, then the advantages go away. When the objects get modified very frequently, the overhead of checking that the deep copy has been performed before modifying the object makes it perform worse than just a plain deep copy.
Strings are often cited as good places for lazy copying, since very often, copies are just made for displaying in different places, but most strings either use deep copies anyway, or use shallow copies, and disallow modification altogether.
Lazy Copy is used when you might generally need Deep Copy, but are not sure whether it is really necessary. Deep Copy is generally an expensive operation. If you do it unconditionally in 100% of cases and then discover that you only needed it in 10% of objects, then the efforts spent on Deep-Copying the other 90% of objects were wasted.
This is when Lazy Copy comes in. Lazy Copy is a postponed, delayed, on-demand version of Deep Copy. With Lazy Copy you don't perform Deep Copy right away. Instead, you prepare (or schedule) a Deep Copy by storing all pertinent information in the recipient object (which most of the time boils down to a mere Shallow Copy) and wait till it becomes known whether the Deep Copy is really necessary for that specific object. If it turns out to be necessary, you perform the actual Deep Copy. If the need for Deep Copy never arises, then it doesn't happen, thus saving you the effort.
Let me speak about C++
One of the very important part writing class in C++ is to implement copy constructor and overloaded = operator function.
This tutorial talks about the need of writing these functions in order to make your program more effective. Before getting into the concept lets understand basic terminology.
Constructor : Its a special function called when object of class is created. Ideally a constructor must contain the logic to initialize the data members of the class.
Copy Constructor: It is called when and object is initialized at the time of creation. There exist more scenario when an copy constructor is called.
Operator function: C++ allows to overload operator with the help of operator keyword. This helps us to treat user defined types as basic types.
Default copy constructor and =operator function which is inserted by compiler incase they are missing from the class. It performs a bit pattern copy i.e. simply copies data members of one object into another object.
Consider following code sample
class CSample
{
int x;
public:
//dafualt constructor
CSample(): x(0){}
int GetX()
{
return x;
}
};
int main()
{
CSample ob1; //Default constructor is called.
CSample ob2 = ob1; //default copy constructor called.
CSample ob3; //Default constructor called.
ob3 = ob1; //default overloaded = operator function called.
}
In the above example
CSample ob2 = ob1;
//This line will copy the bit pattern of ob1 in ob2 so the data member
// x in both the object will contain same value i.e 0.
Similarly statement
ob3 = ob1;
//This line will copy the bit pattern of ob1 in ob2 so the data member
// x in both the object will contain same value i.e 0.
The above code will work as expected until the class member is not allocated any resource (file or memory). Consider a scenario where the class is updated as follow.
class CSample
{
int *x;
int N;
public:
//dafualt constructor
CSample(): x(NULL){}
void AllocateX(int N)
{
this->N = N;
x = new int[this->N];
}
int GetX()
{
return x;
}
~CSample()
{
delete []x;
}
};
int main()
{
CSample ob1; //Default constructor is called.
ob1.AllocateX(10);
//problem with this line
CSample ob2 = ob1; //default copy constructor called.
CSample ob3; //Default constructor called.
//problem with this line
ob3 = ob1; //default overloaded = operator function called.
}
class CSample
{
int *x;
int N;
public:
//dafualt constructor
CSample(): x(NULL)
{}
//copy constructor with deep copy
CSample(const CSample &ob)
{
this->N = ob.N:
this->x = new int[this->N];
}
//=operator function with deep copy.
void operator=(const CSample &ob)
{
this->N = ob.N:
this->x = new int[this->N];
}
void AllocateX(int N)
{
this->N = N;
x = new int[this->N];
}
int GetX()
{
return x;
}
~CSample()
{
delete []x;
}
};