It was my understanding that the C++ compiler generates assignment operators like this:
struct X {
std::vector<int> member1;
std::vector<int> member2;
X& operator=(const X& other) {
member1 = other.member1;
member2 = other.member2;
}
};
Isn't this exception-unsafe? If member2 = other.member2
throws, then the original assignment's side effects are not undone.
The assignment operator = assigns the value of its right-hand operand to a variable, a property, or an indexer element given by its left-hand operand. The result of an assignment expression is the value assigned to the left-hand operand.
It is well-known what the automatically-generated assignment operator will do - that's defined as part of the standard and a standards-compliant C++ compiler will always generate a correctly-behaving assignment operator (if it didn't, then it would not be a standards-compliant compiler).
The basic advantage of compound assignment operators is that it saves a lot of code within the Java language program.
If no user-defined copy assignment operators are provided for a class type (struct, class, or union), the compiler will always declare one as an inline public member of the class.
the compiler will try to generate them for you. Even if you’ve hand-written the other two. In some cases it may not succeed though, for instance if the class contains a const or reference member, the compiler won’t be able to come up with an operator=. If you write any of the following:
Declaring a copy constructor doesn't suppress the compiler-generated copy assignment operator, and vice-versa. If you implement either one, we recommend that you implement the other one, too. When you implement both, the meaning of the code is clear.
Assignment operator. The assignment operator is used to assign the value of one object to another object, for example, a = b. It differs from the copy constructor in that the object being assigned to already exists. Some guidelines for implementing the assignment operator include:
Compiler warnings that are off by default. The compiler supports warnings that are turned off by default, because most developers don't find them useful. In some cases, they warn about a stylistic choice, or about common idioms in older code. Other warnings are about use of a Microsoft extension to the language.
Using the 4 level exception safety system:
The compiler generated assignment operator has the basic exception guarantee if each of the members of the object provides the basic exception guarantee or "better", and if the objects invariants do not have intra-member dependencies.
It has the no throw guarantee if each member's assignment also has the no throw guarantee.
It rarely (if ever) has the strong guarantee, which is possibly what you are referring to as "exception unsafe".
The copy-swap idiom is popular because writing no-throw swap
is often easy, and constructors should already provide the strong exception guarantee. The result is an operator=
with the strong exception guarantee. (In the case of move
, it is pseudo-strong, as the input is often not rolled back, but it is an rvalue)
void swap( Foo& other ) noexcept; // implement this
Foo& operator=( Foo const& f ) {
Foo tmp(f);
swap( tmp );
return *this;
}
Foo& operator=( Foo && f ) {
Foo tmp(std::move(f));
swap( tmp );
return *this;
}
If you also take the copy by-value, you can sometimes upgrade operator=
to being no throw (with the only exception being possibly in the construction of the argument).
Foo& operator=( Foo f ) noexcept {
swap( f );
return *this;
}
in some cases, some constructors of Foo
are noexcept
and others are not: by taking the other by-value, we provide the best exception guarantee we can in total (as the argument to =
can sometimes be directly constructed, either by elision or direct construction via {}
).
It is not practical for the language (at least at this time) to implement a copy-swap operator=
with the strong guarantee for a few reasons. First, C++ operates on "you only pay for what you use", and copy-swap can be more expensive than memberwise copy. Second, swap
is not currently part of the core language (there are some proposals to add operator :=:
and fold it in). Third, reverse compatibility with previous versions of C++ and with C.
Edited 4 June 2014
My initial answer was based on my understanding that the original poster sought an assignment operator guaranteed not to throw an exception. As per various commenters, it has become apparent that exceptions are OK as long as the object is left un-altered on an exception.
The suggested way to do this is via temp variables and std::swap().
X& X::operator=(const X& other)
{
// assign to temps. If this throws, the object
// has not changed.
auto m1 = other.member1;
auto m2 = other.member2;
// the theory is, that swap won't throw
// can we rely on that?
std::swap(m1, member1);
std::swap(m2, member2);
return *this;
}
In fact we cannot rely that swap() won't throw an exception, unless we know a bit about the components of our object.
To be sure that swap() will never throw, we need the member objects to be Move Assignable and Move Constructible.
In the example given, with a C++11 or later compiler the std::vector<int> is move assignable and move constructible, so we are safe. However as a general solution we always need to be aware of any assumptions we are making and to check that they are holding.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With