A trivial Copy
Recap Regularity
A few posts ago we set the goal to make our optional. Until now, we implemented almost all of them.
Operation | C++ Syntax | State of Implementation |
---|---|---|
default constructor | T a; |
implemented in “Constructors for valid optionals” |
copy constructor | T a = b; |
not implemented yet |
assignment | a = b; |
not implemented yet |
equality | a == b |
implemented in “Equality and Inequality” |
inequality | a != b |
implemented in “Equality and Inequality” |
ordering | a < b |
implemented in “Ordering Optionals” |
Only copy construction and assignment are left. As both operations are basically creating a copy of an optional, we will cover them both at once.
One may come up with a definition of copy, that involves copying all members one by one, but as we already defined equality, it is easier – I suppose – to define copy in terms of equality:
An optional and it’s copy are equal.
Yes, it’s as simple as that. For our current optional implementation, this is completely sufficient.
Deriving Tests
We can now derive test cases from this definition. We have to cover two different operations: copy construction and assignment. Additionally we should cover optionals with and without a value for both operations. This makes a total of four tests.
Please note that I have chosen to use a single TEST_CASE
and multiple SECTION
s. Each of this SECION
s introduces
it’s own scope, so we can reuse the variable
names. We could also have created four different test cases. Before we get to the reason, why I didn’t, let’s have a
look at a neat little feature of catch: the --list-test-names-only
flag. If you pass it to a test executable, it
will print out a list of all test names. If we do so with our test executable ./test_optionalcpp
--list-test-names-only
, we get this output.
This now reads more like a description of our optional’s features. We explicitly have chosen to name our tests in a way, that the become “they are executable specification” . Using this flag, we can now actually print out the specification of our optional.
Getting back to the question, why I have chosen to add one test case with four sections instead of four test cases, you might have already a rough idea about my reason. Above we came up with one simple rule, how copy is supposed to work. This rule should be sufficient – more rules are not needed. Using the “one test cases, four sections”-approach, we have added one more line to our list of test names. With the “four test cases” approach we would have added four test names. This would have somewhat polluted our list of test names – and therefore our specification. The specification, as it is now, is pretty concise, which is nice.
If we now compile our tests and run them, we will see that they all pass!. We didn’t do anything the make the pass. How can that be?
Trivial Types
Our optional, as it is right now, has tow members: an boolen (bool
) and a integer (unsigned int
). Both of them
are scalar types build into the language and therefore they are
trivial copyable. In general this means, that both of
them can be copied byte by byte without any issues (please note, you can not do that with types like e.g. std::string
which contain (owning) pointers or references).
We can put it this way: trivial copyable types can be copied without using a copy constructor or assignment.
As both members of our optional are trivial copyable and we haven’t defined neither a copy constructor, a copy
assignment operator, nor a destructor, our optional is trivial copyable as well. In this case, the compiler will
generate the needed copy constructor and the assignment operator for us: we don’t need to define them.
Actually we shouldn’t define them in this case because of basically two reasons:
- It is in general advisable to define none of these operations if not needed. This is also known as the ‘rule of zero’ or C.20 of the C++ Core Guidelines.
- As soon as we define one of these operations on our own, the class type becomes immediately non-trivial (see this tiny example in compiler explorer). It would be a shame, if a type which could be trivial is not, because of a superfluous copy constructor. There are places, where basically only trivial types can used, e.g. std::atomic (if no template specialization exists, which is usually not the case for user defined types).
If you want to dive deeper in to the topic of trivial types in C++, you should have a look at Jason Turners Talk “Great C++ is_trivial”.
So, we are finished now: we have made our optional regular!
Conclusion
In this post we got to know trivial copyable types and the ‘rule of zero’. We now know for sure, that our optional is regular now. Our optional is pretty much usable now, beside one remaining issue, which we will tackle in the upcoming post.