When any allocation of the new container or copying of the elements fails with an exception, will the current contents of the target container remain intact?

- 165,132
- 21
- 377
- 458

- 2,817
- 9
- 22
-
2Copying should by definition never manipulate the original data. Move operations are where this question is relevant. – Eric Dec 20 '21 at 12:48
-
I'm not concerned about the source data but the destination-data. Is it guaranteed not to be overwritten when copying did throw an exception ? – Bonita Montero Dec 20 '21 at 12:51
-
3"Atomically" is not the term you're reaching for. Search for "exception guarantees in C++ containers". You might start here: https://en.cppreference.com/w/cpp/language/exceptions – Marshall Clow Dec 20 '21 at 12:54
-
1Copying a container is not done *atomically* (which means something in C++, regarding multithreading, and not how the term is being used in the question). Exceptions in the midst of copying will leave the original container in a well-defined state (assuming the type it holds are well-behaved types), and the copied into container in a well-defined (albeit incomplete copy) state. See Appendix E of Stroustrup's **The C++ Programming Language**. – Eljay Dec 20 '21 at 12:55
-
@Bonita Montero So you are specifically talking about the original data of the destination container when a copy assignment fails? Maybe you should clarify that in the question. At least to me that wasn't clear. – Eric Dec 20 '21 at 12:55
-
@Eric: There's nothing to clarify here because the source-container is always Container const &. – Bonita Montero Dec 20 '21 at 12:59
-
The __to__ container can be a file (for example) using `std::back_inserter` and changes like this are not transactional. In general the destination std::containers are not transactional but will remain in a valid state with the elements up to the throwing one copied. – Richard Critten Dec 20 '21 at 13:01
-
@RichardCritten As you can see from my question I'm concerned about copying the container a whole through =. – Bonita Montero Dec 20 '21 at 13:03
-
2@BonitaMontero there as been so much requested clarification please post a [mcve] to make your question clear. How are we supposed to know you don't mean `std::copy` for example ? ie are you copy constructing, assigning or using the iterator based constructors ? – Richard Critten Dec 20 '21 at 13:07
-
@RichardCritten: There's no minimal reproducible example here since this would be a one-liner and this would apply to many containers. It's just assigning to another container to = with non-moving. – Bonita Montero Dec 20 '21 at 13:14
-
2The only guarantees made, when an exception is thrown while copying a container, are that the source container (being copied from) does not change, and the destination container (being copied to) is in a valid state that may be be safely destructed. – Peter Dec 20 '21 at 13:31
-
A [mcve] of a container that has objects of which one throws an exception part of the way through the copy assignment would be most helpful here, and probably will illustratively answer your question. – Eljay Dec 20 '21 at 13:36
-
@Eljay: The minimal reproducible example is: typeXContainerA = typeXContainerB; Complicated, eh ? – Bonita Montero Dec 20 '21 at 13:40
-
Does `typeXContainerA = typeXContainerB;` throw an exception midway through the copy operation? This line of code does not compile. It is an incomplete program. – Eljay Dec 20 '21 at 13:42
-
@BonitaMontero given the amount of misunderstanding the [mcve] would have been useful and as you say an easy 1 liner to provide. – Richard Critten Dec 20 '21 at 13:50
-
@RichardCritten: There's no minimal reproducible example since the behaviour of what I asked for is hidden inside the implementation of the copy-assignment operator of the containers. They might do the copy atomically or they don't, i.e. the destination-container is overwritten only if all allocations and copies have finished successfully if this is mandated by the standard or just by the individual implementation. My question is just if there's a atomic-ness mandated by the standard. – Bonita Montero Dec 20 '21 at 15:11
-
1Does this answer your question? [Where can I find all the exception guarantees for the Standard Containers and Algorithms?](https://stackoverflow.com/questions/11699083/where-can-i-find-all-the-exception-guarantees-for-the-standard-containers-and-al) TLDR: no requirements for strong exception safety for container copy – dewaffled Dec 20 '21 at 15:52
2 Answers
The exception safety guarantee do we have when copying a C++ container is the basic guarantee.
When you copy a container A to a container B, if an exception is thrown, the container B will not remain intact, its status will be partially modified (partially overwritten).
As Eljay says: Exceptions in the midst of copying will leave the original container in a well-defined state (assuming the type it holds are well-behaved types), and the copied into a container in a well-defined (albeit incomplete copy) state.
All C++ Standard Library code provides (at least) a basic guarantee.
Atomicity or strong guarantee or prevent partial objects created is not mandated by the standard, i think, because strong guarantee can be expensive.
You should copy the contents of a source container into an intermediate container, if there are no problems (no throws), move the contents of the intermediate container into the target container, otherwise, throw away the intermediate container (or something like the copy-and-swap idiom), an example.
Anyway, you can see the assignment operator implementation.
I hope I’ve helped at least a little, even if I’m a beginner.
Edit :
class some_type final {
public:
constexpr some_type() noexcept : data(0) { }
explicit constexpr some_type(const int d) noexcept : data(d) { }
some_type(const some_type& other) : data(other.data) { error(); }
auto& operator=(const some_type& other) {
data = other.data;
error();
return *this;
}
constexpr auto get() const noexcept { return data; }
private:
static unsigned int copy_count;
int data;
void error() { if (++copy_count == 7) throw std::exception(); }
};
unsigned int some_type::copy_count = 0U;
Example 1 :
int main()
{
std::vector src{ some_type(3), some_type(2), some_type(1), some_type(0)}; // +4 copies
std::vector<some_type> dest{ some_type(5) }; // +1 copy (5 copies)
std::cout <<
"\nDest Size : " << dest.size() << // size : 1
"\nDest Capacity : " << dest.capacity() << // capacity : 1
"\nDest Values : ";
for (const auto& value : dest)
std::cout << value.get() << ' '; // values : 5
try {
dest = src; // +4 copies (more than 7 copies, THROW!)
} catch (...) {
std::cout <<
"\n"
"\nDest Size : " << dest.size() << // size : 0
"\nDest Capacity : " << dest.capacity() << // capacity : 4
"\nDest Values : ";
for (const auto& value : dest)
std::cout << value.get() << ' '; // values :
std::cerr << "\n\nUnknown error : Something went wrong\n";
return 1;
}
}
Example 2 :
int main()
{
std::vector src{ some_type(3), some_type(2), some_type(1), some_type(0)}; // +4 copies
std::vector<some_type> dest;
dest.reserve(4);
dest = { some_type(5) }; // +1 copy (5 copies)
std::cout <<
"\nDest Size : " << dest.size() << // size : 1
"\nDest Capacity : " << dest.capacity() << // capacity : 4
"\nDest Values : ";
for (const auto& value : dest)
std::cout << value.get() << ' '; // values : 5
try {
dest = src; // +4 copies (more than 7 copies, THROW!)
} catch (...) {
std::cout <<
"\n"
"\nDest Size : " << dest.size() << // size : 1
"\nDest Capacity : " << dest.capacity() << // capacity : 4
"\nDest Values : ";
for (const auto& value : dest)
std::cout << value.get() << ' '; // values : 3
std::cerr << "\n\nUnknown error : Something went wrong\n";
return 1;
}
}
If you print the dest values you will see that it is partially copied.
I just had an idea: I simply created a vector and did a copy-assignment on it through another vector. The first vector's size was larger than the sencond's before the copy-assignment. But as the capacity was the former size before a atomic implementation coudln't be possible under this circumstance.
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<size_t> toBeOverwritten;
toBeOverwritten.resize( 1000, 0 );
vector<size_t> overwriteFrom;
overwriteFrom.resize( 500, 0 );
toBeOverwritten = overwriteFrom;
cout << toBeOverwritten.capacity() << endl;
}

- 2,817
- 9
- 22