If each object has a unique, permanent identity, then you probably can't really support copying--a copy of an object should be the same as the original object, and in this case you're saying each should be unique, so having a copy probably doesn't make sense.
Move construction, on the other hand, should be more reasonable. Although an ID moves from one object to another, there's still only one object with a given identity at a given time.
The move ctor is pretty simple and straightforward. Just initialize your members in the member initializer list like usual, including the fact that since it's a move ctor, you want to move from the unique_id in the source:
Example(Example&& other)
: unique_id(std::move(other.unique_id))
, attribute1(other.attribute1)
, attribute2(other.attribute2)
{
}
In theory, you probably should use std::move on attribute1 and attribute2 as well, but since they're ints, it won't normally make any real difference.
Then we get to the one that's a little bit non-trivial: move assignment. Since we've defined unique_id as const, we can't just copy over it. To do the job, we first destroy the current contents of the destination object, then use placement new to do a move construction from the source to the destination:
Example &operator=(Example&& other) {
this->~Example();
new (this) Example(std::move(other));
return *this;
}
This works because const qualification has no effect during destruction or construction, so that's basically the only way we can implement our assignment.
Here's a sample demonstrating it all in action:
#include <string>
#include <new>
#include <iostream>
class Example {
public:
Example(const std::string& unique_id_, int attribute1_, int attribute2_):
unique_id(unique_id_), attribute1(attribute1_), attribute2(attribute2_){};
void set_attribute1(int attribute1_){ attribute1 = attribute1_; }
void set_attribute2(int attribute2_){ attribute2 = attribute2_; }
/* Deleting copy/move/assignment operators to make each instance unique */
Example(const Example&) = delete;
Example& operator= (Example&) = delete;
Example(Example&& other)
: unique_id(std::move(other.unique_id))
, attribute1(other.attribute1)
, attribute2(other.attribute2)
{
}
Example &operator=(Example&& other) {
this->~Example();
new (this) Example(std::move(other));
return *this;
}
private:
const std::string unique_id;
int attribute1;
int attribute2;
friend std::ostream &operator<<(std::ostream &os, Example const &e) {
return os << "ID: " << e.unique_id;
}
};
int main() {
Example a("A", 1, 2);
std::cout << "A: " << a << "\n";
Example b{std::move(a)}; // move construction
std::cout << "B: " << b << "\n";
Example c("B", 3, 4); // construct a destination object
c = std::move(b); // move assign into it
std::cout << "C: " << c << "\n";
}
As to real-life examples of unique ids in objects...without going back on searching through code, I'm not sure. I can remember a few that seem like they're probably at least pretty close (and certainly others that have const members, that use the same destroy/placement new "trick" to do assignment), but I'm not quite ambitious enough to look through them to check whether there are any that are quite identical to this or not.