Suppose I have a weird string type, that either owns or doesn't own it's underlying buffer:
class WeirdString {
private:
char* buffer;
size_t length;
size_t capacity;
bool owns;
public:
// Non-owning constructor
WeirdString(char* buffer, size_t length, size_t capacity)
: buffer(buffer), length(length), capacity(capacity), owns(false)
{ }
// Make an owning copy
WeirdString(WeirdString const& rhs)
: buffer(new char[rhs.capacity])
, length(rhs.length)
, capacity(rhs.capacity)
, owns(true)
{
memcpy(buffer, rhs.buffer, length);
}
~WeirdString() {
if (owns) delete [] buffer;
}
};
Does that copy constructor violate the standard somewhere? Consider:
WeirdString get(); // this returns non-owning string
const auto s = WeirdString(get());
s
is either owning or non-owning depending on whether or not the additional copy constructor got elided, which in C++14 and earlier is permitted but optional (though in C++17 is guaranteed). That Schrödinger's ownership model suggests that this copy constructor is, in itself, undefined behavior.
Is it?
A more illustrative example might be:
struct X {
int i;
X(int i)
: i(i)
{ }
X(X const& rhs)
: i(rhs.i + 1)
{ } ~~~~
};
X getX();
const auto x = X(getX());
Depending on which copies get elided, x.i
could be 0, 1, or 2 more than whatever was returned in getX()
. Does the standard say anything about this?