I'm a C newbie, and this is probably a stupid question to ask.
I'm reading a book Data Structures and Algorithm Analysis in C , where the implementation of a user-defined vector class template was provided.
Here's a snippet of it
// Move Constructor
Vector(Vector&& source) noexcept
: size(source.size), capacity(source.capacity), objects(source.objects)
{
source.objects = nullptr;
source.size = 0;
source.capacity = 0;
}
// Move Assignment
Vector & operator= (Vector&& source) noexcept
{
std::swap(size, source.size);
std::swap(capacity, source.capacity);
std::swap(objects, source.objects);
return *this;
}
I figure some operations are redundant, here's my draft↓
// Move Constructor
Vector(Vector&& source) noexcept
: size(source.size), capacity(source.capacity), objects(source.objects)
{
source.objects = nullptr; // Forget the rest two integers
}
// Move Assignment
Vector & operator= (Vector&& source) noexcept
{
size = source.size; // std::swap is unnecessary
capacity = source.capacity;
std::swap(objects, source.objects);
return *this;
}
Why would the author use std::swap
here? To my knowledge, std::swap
is implemented using move semantics, which is introduced to fix smart pointers, not for basic data types.
size
and capacity
are merely integers, std::swap
actually calls std::move
and does the assignment three times, thus slowing down the performance since an ordinary assignment would do the same thing. Or is there any benefit of doing so?
Re: Much obliged for the comments folks, I can understand that there is no penalty for using std::move, but swapping means three assignments, right? Can I just use a = operator instead?
CodePudding user response:
The author of this code makes the entirely reasonable assumption that the implementation has a decent implementation of std::swap<size_t>
.
There are a number of reasons why this can be the case, but the two major reasons are:
- The optimizer will likely put the involved variables in registers, making the swap effectively free. Compilers track which register holds which value; so you the compiler can swap its associated bookkeeping without swapping physical registers. This is a pure compile-time operation
- If this wouldn't have worked, the library implementation can specialize
std::swap<size_t>
and other critical algorithms. You probably looked at the non-specialized template.
CodePudding user response:
I believe it might be possible to implement a vector-like class template such that the pointer data objects
being nullptr
and size
and capacity
being nonzero would be a valid vector state. However, this would bring significant overhead into almost all vector operations. Consider just a simple push_back
(I appended underscore to the private member variables for clarity):
size_t size() const
{
return objects_ == nullptr ? 0 : size_;
}
size_t capacity() const
{
return objects_ == nullptr ? 0 : capacity_;
}
void push_back(const T& value)
{
if (size() == capacity()) // branching overhead here
// preform reallocation first
...
If we instead insist being all objects
, size
, and capacity
in a consistent state, this would simply be:
void push_back(const T& value)
{
if (size_ == capacity_) // no unnecessary branching here
...