Should an STL container avoid copying elements int

2020-05-25 03:17发布

问题:

The question is about self-assignment. For example copying a vector into itself:

std::vector<std::string> vec(5, "hello");
vec = vec;

Should the code above perform 5 assignment operations of strings into themselves, or just do nothing? I mean whether the following checking is valid:

std::vector operator=(const std::vector &rhs)
{
    if (this == &rhs)
        { return *this; }
    ...
}

I'm working on my own implementation of std::variant class (just for fun) and interested if I should add a self-assignment check to the beginning of the assignment operator, or should I just copy the contained element into itself?

I understand that generally this doesn't matter. You should not make a class that utilizes the fact of copying into itself. But I'm interested if the standard says anything about this.

回答1:

The pre/post conditions of assignment of a container specified by the standard (quoting latest draft):

[tab:container.req]

r = a

Ensures: r == a.

This allows but does not mandate self assignment check.



回答2:

interested if I should add a self-assignment check to the beginning of the assignment operator, or should I just copy the contained element into itself?

C++ Core Guidelines recommends not to do a self-assignment check in a user class if all of its members are self-assignment safe:

Enforcement (Simple) Assignment operators should not contain the pattern if (this == &a) return *this;???

It is for an efficiency reason. Self-assignments are unlikely to happen in practice. They are rare, and so it is better to avoid doing a self-assignment check in every operation. A self-assignment check probably makes the code faster in self-assignment case (very rare) and makes it slower in all other cases (more common).

Imagine you assign a million elements. In every assignment operation a self-assignment check is done. And it is most probably that it is done for nothing because none of the assignments is actually a self-assignment. And so we do a million useless checks.

If we skip doing a self-assignment check then nothing bad happens except that if self-assignment really happens then we do useless self-assignments of all members (that is sometimes slower than doing a single self-assignment check at the beginning of the assignment operator). But if your code does a million self-assignments it is a reason to reconsider your algorithm rather than to perform a self-assignment check in all of the operations.

However, self-assignment check still must be used for classes that are not self-assignment safe by default. The example is std::vector. The vector, that is being copied into, first has to delete the existing elements. But if the destination vector and source vector is the same object, then by deleting the elements in destination vector we also delete them in the source vector. And so it won't be possible to copy them after deletion. That is why libstdc++ does a self-assignment check for std::vector (though it is possible to implement std::vector without self-assignment check).

But it doesn't do it for std::variant for example. If you copy a variant into itself then the contained value will be copied into itself. See live example. Because copying it into itself is self-assignment safe (provided the contained value is self-assignment safe).

Thus, libstdc++ does a self-assignment check for std::vector (for providing self-assignment safety), and doesn't for std::variant (for efficiency).



回答3:

[...] if I should add this checking to the beginning of the assignment operator [...] ?

You should, regardless whether std::vector, or other STL containers do that for you. Imagine a user that works with your library and does x = x, outside of STL containers scope.

Now to the STL container requirements - I believe the standard does not specify whether assignment is required to perform a check for being a self-assignment (went through the majority of Containers library section). This gives room for compiler optimisations and I believe that a decent compiler should perform such checks.



回答4:

Checking this == &rhs is actually a pretty well-known idiom, and helps to be sure that you don't break anything by guaranteeing that lhs and rhs are different objects. So, this is valid and actually encouraged.

I don't know whether STL containers are required to do the check, though.