Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are compiler-generated assignment operators unsafe?

Tags:

c++

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.

like image 535
Puppy Avatar asked Jun 03 '14 14:06

Puppy


People also ask

What is true about the assignment (=) operator?

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.

Is assignment operator automatically generated?

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).

What are the advantages of assignment operator?

The basic advantage of compound assignment operators is that it saves a lot of code within the Java language program.

What happens if you dont define a copy assignment operator?

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.

Why can’t the compiler generate operators for me?

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:

Does declaring a copy constructor suppress the compiler-generated copy assignment operator?

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.

What is the use of assign operator?

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:

Why are Compt compiler warnings turned off?

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.


2 Answers

Using the 4 level exception safety system:

  • No throw
  • Strong Guarantee -- operation completes, or is rolled back completely
  • Basic Guarantee -- invariants are preserved, no resources leaked
  • No Guarantees

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.

like image 177
Yakk - Adam Nevraumont Avatar answered Oct 30 '22 03:10

Yakk - Adam Nevraumont


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.

like image 34
Michael J Avatar answered Oct 30 '22 01:10

Michael J