Are reference members good practice? Are const members? - c++

A coworker and I are debating whether const or reference members are ever the right thing to do. const and reference members make a class noncopyable and unmovable unless you write your own copy and move operators which ignore the reference or const members. I can't find a case where ignoring copying or moving certain members just because they are references or const makes sense.
I think having an unmovable object is very rarely logically sound, and is a choice that only has something to do with whether the class is location invariant. A noncopyable object is much more common, but the choice to make a class noncopyable has to do with whether it holds a resource which is logically noncopyable, if it represents sole ownership, etc. I can't think of a reason the sole trait of having a reference or const member or not would imply that the class should have either of these traits (noncopyable or unmovable)
Should they ever be used? Should one but not the other? What are examples?

I can't think of a reason the sole trait of having a reference or const member or not would imply that the class should have either of these traits (noncopyable or unmovable)
I think the premise is wrong.
Having a const data member or a reference data member does not make the class non-copyable: it simply makes it impossible to assign it or move from it in a destructive way.
That is because a destructive move operation is not sound for const objects or references: it would be against their semantics, since the state of a const object shall never be altered, and references cannot be unbound.
However, the fact that X is not copy-assignable or move-assignable does not compromise the possibility of constructing copies of those objects, which is - as you point out - a sound operation. The following program, for instance, will compile fine:
struct X
{
X() : x(0), rx(x) { }
const int x;
const int& rx;
};
int main()
{
X x;
X x1 = x; // Copy-initialization (copy-constructs x1 from x)
X x2 = X(); // Copy-initialization (copy-constructs x2 from a temporary)
}
The compiler will generate a copy-constructor implicitly for you, and moves will degenerate to copies. If this degeneration of move into copy is inappropriate for the semantics of your class, then you may consider not having const or reference members.

Related

Why does std::copyable subsume std::movable?

According to cppreference, std::copyable is defined as follows:
template <class T>
concept copyable =
std::copy_constructible<T> &&
std::movable<T> && // <-- !!
std::assignable_from<T&, T&> &&
std::assignable_from<T&, const T&> &&
std::assignable_from<T&, const T>;
I'm wondering why a copyable object should be also movable. Just think about a global variable that is accessed by several functions. While it makes sense to copy that variable (for example to save its state before calling another function) it makes no sense, and actually would be very bad, to move it since other functions might not know that that variable is currently in an unspecified state. So why exactly does std::copyable subsume std::movable ?
This comes from two facts. Firstly, even if you don't define move constructor + move assignment you can still construct/assign object from r-value reference if you define copying functions. Just take a look at the example:
#include <utility>
struct foo {
foo() = default;
foo(const foo&) = default;
foo& operator=(const foo&) = default;
};
int main()
{
foo f;
foo b = std::move(f);
}
Secondly (and maybe more importantly), the copyable type can always be (or according to standard now must be) also movable in some way. If object is copyable then worst case scenario for move is just copying internal data.
Note that since I declared copy constructor the compiler DID NOT generate default move constructor.
While it makes sense to copy that variable (for example to save its state before calling another function) it makes no sense, and actually would be very bad, to move it since other functions might not know that that variable is currently in an unspecified state.
There's a strong, unstated presumption here of what moving actually means that is probably the source of confusion. Consider the type:
class Person {
std::string name;
public:
Person(std::string);
Person(Person const& rhs) : name(rhs.name) { }
Person& operator=(Person const& rhs) { name = rhs.name; return *this; }
};
What does moving a Person do? Well, an rvalue of type Person can bind to Person const&... and that'd be the only candidate... so moving would invoke the copy constructor. Moving does a copy! This isn't a rare occurrence either - moving doesn't have to be destructive or more efficient than copying, it just can be.
Broadly speaking, there are four sane categories of types:
Types for which move and copy do the same thing (e.g. int)
Types for which move can be an optimization of copy that consumes resources (e.g. string or vector<int>)
Types which can be moved but not copied (e.g. unique_ptr<int>)
Types which can be neither moved nor copied (e.g. mutex)
There are a lot of types that fall into group 1 there. And the kind of variable mentioned in OP should also fall into group 1.
Notably missing from this list is a type that is copyable but not movable, since that makes very little sense from an operational stand-point. If you can copy the type, and you don't want destructive behavior on moving, just make moving also copy the type.
As such, you can view these groups as a kind of hierarchy. (3) expands on (4), and (1) and (2) expand on (3) - you can't really differentiate (1) from (2) syntactically. Hence, copyable subsumes movable.

Implementing copy\move operator= for classes with const members [duplicate]

So I have some pretty extensive functional code where the main data type is immutable structs/classes. The way I have been declaring immutability is "practically immutable" by making member variables and any methods const.
struct RockSolid {
const float x;
const float y;
float MakeHarderConcrete() const { return x + y; }
}
Is this actually the way "we should do it" in C++? Or is there a better way?
The way you proposed is perfectly fine, except if in your code you need to make assignment of RockSolid variables, like this:
RockSolid a(0,1);
RockSolid b(0,1);
a = b;
This would not work as the copy assignment operator would have been deleted by the compiler.
So an alternative is to rewrite your struct as a class with private data members, and only public const functions.
class RockSolid {
private:
float x;
float y;
public:
RockSolid(float _x, float _y) : x(_x), y(_y) {
}
float MakeHarderConcrete() const { return x + y; }
float getX() const { return x; }
float getY() const { return y; }
}
In this way, your RockSolid objects are (pseudo-)immutables, but you are still able to make assignments.
I assume your goal is true immutability -- each object, when constructed, cannot be modified. You cannot assign one object over another.
The biggest downside to your design is that it is not compatible with move semantics, which can make functions returning such objects more practical.
As an example:
struct RockSolidLayers {
const std::vector<RockSolid> layers;
};
we can create one of these, but if we have a function to create it:
RockSolidLayers make_layers();
it must (logically) copy its contents out to the return value, or use return {} syntax to directly construct it. Outside, you either have to do:
RockSolidLayers&& layers = make_layers();
or again (logically) copy-construct. The inability to move-construct will get in the way of a number of simple ways to have optimal code.
Now, both of those copy-constructions are elided, but the more general case holds -- you cannot move your data from one named object to another, as C++ does not have a "destroy and move" operation that both takes a variable out of scope and uses it to construct something else.
And the cases where C++ will implicitly move your object (return local_variable; for example) prior to destruction are blocked by your const data members.
In a language designed around immutable data, it would know it can "move" your data despite its (logical) immutability.
One way to go about this problem is to use the heap, and store your data in std::shared_ptr<const Foo>. Now the constness is not in the member data, but rather in the variable. You can also only expose factory functions for each of your types that returns the above shared_ptr<const Foo>, blocking other construction.
Such objects can be composed, with Bar storing std::shared_ptr<const Foo> members.
A function returning a std::shared_ptr<const X> can efficiently move the data, and a local variable can have its state moved into another function once you are done with it without being able to mess with "real" data.
For a related technique, it is idomatic in less constrained C++ to take such shared_ptr<const X> and store them within a wrapper type that pretends they are not immutable. When you do a mutating operation, the shared_ptr<const X> is cloned and modified, then stored. An optimization "knows" that the shared_ptr<const X> is "really" a shared_ptr<X> (note: make sure factory functions return a shared_ptr<X> cast to a shared_ptr<const X> or this is not actually true), and when the use_count() is 1 instead casts away const and modifies it directly. This is an implementation of the technique known as "copy on write".
Now as C++ has developed, there are more opportunities for elision. Even C++23 is going to have more advanced elision. Elision is when the data isn't logically moved or copied, but just has two different names, one inside a function and one outside.
Relying on that remains awkward.
As of c++20, workarounds with getters is no longer needed.
You can now define your own copy-assignment operator for classes that contain const member objects without undefined behavior as of c++20.
This was undefined behavior prior to c++ and remains so for complete const objects but not non-const objects with const members.
https://stackoverflow.com/a/71848927/5282154

Move constructor involving const unique_ptr

In the code below, I made p const because it will never point to any other int during Foo's lifetime. This doesn't compile, as the unique_ptr's copy constructor is called, which is obviously deleted. Are there any solutions besides making p non-const? Thanks.
#include <memory>
using namespace std;
class Foo
{
public:
//x is a large struct in reality
Foo(const int* const x) : p(x) {};
Foo(Foo&& foo) : p(std::move(foo.p)) {};
private:
const unique_ptr<int> p;
};
The semantics of your move constructor are contradictory.
You have declared a const std::unique_ptr which will (uniquely) own the value it is initialised with.
But you've declared a move constructor that should move that value into another object at construction.
So what do you think should happen to the std::unique_ptr in the 'temporary' being move constructed from?
If you want it to be release()ed you've violated its constness.
If you want it to retain its value you've violated the constraint of std::unique which requires no more than one such object to own any given object.
Checkmate.
This problem reveals a subtle limitation of the C++ language. It requires move semantics to leave the copied to and from as valid objects.
There are several quite reasonable proposals for 'destructive move' which would in truth better reflect what most uses of move are doing - take a value to here from there 'invalidating' what was there.
Google them. I haven't made a literature survey so don't want to recommend one.
Your alternatives here are to remove const or cast it way.
I strongly recommend removing it. You can make sure the semantics of your class ensure the appropriate const-ness with no impact and no 'ugly suspect' const_cast.
#include <iostream>
#include <memory>
class Foo
{
public:
Foo(const int x) : p(new int(x)) {};
Foo(Foo&& foo) :
p(std::move(foo.p)) {
};
int get(void)const{
return *(this->p);
}
private:
std::unique_ptr<int> p;
};
Foo getMove(){
return Foo(88);
}
int main(){
Foo bar(getMove());
std::cout<<bar.get()<<std::endl;
return EXIT_SUCCESS;
}
To understand why your code does not compile, reflect how you have declared Foo class and how move semantics is generally implemented.
Declaring a const unique_ptr<T> p, you mean that p itself will be never modified, but you could still modify the pointed-to object because of T is not const.
But move works on an opposite assumption. This feature uses the idea that is allowed stealing resources from objects and leave them in a empty state (if an empty state make sense). If can be useful, think move as a sort of 'destructive' copy for the moved object.
Writing std::move(foo.p), basically you steal the resource pointed by foo.p and leave it in a safe state, that means assign foo.p to NULL. But foo.p was declared as const, so the operation is not permitted.
Please consider that in your case you don't need to declare p as a const unique_ptr<int>. Simply declare it as unique_ptr<int> and make sure that member functions are declared as const and non-member functions take it as
const unique_ptr<int> p& parameter. In this way you are sure that p will never change along the object lifetime (except in case of move operation).
It is because unique_ptr has only the move-constructor, which means the initialization argument to p cannot be const, while p is const. I think what you wanted was to declare
unique_ptr p;
instead of
const unique_ptr p;
class Foo {
public:
// x is a large struct in reality
Foo(const int* const x) : p(x) {};
Foo(Foo&& foo) : p(std::move(foo.p)) {};
private:
const unique_ptr<int> p;
};
Concept of using std::unique_ptr is representing a sole ownership of an object. What you're trying to achieve is having a Foo class own an object (which is expressed by std::unique_ptr) and making it movable (your move constructor) which makes a contradiction. I would stick with std::unique_ptr or make it shared using std::shared_ptr.
You might want to read this:
Smart Pointers: Or who owns you baby?
If you want to prevent transfer of ownership, you can use a const std::unique_ptr<T>. This is not very useful.
If you want to prevent modifying the object it holds, you can use a std::unique_ptr<const T>.

Idiomatic Way to declare C++ Immutable Classes

So I have some pretty extensive functional code where the main data type is immutable structs/classes. The way I have been declaring immutability is "practically immutable" by making member variables and any methods const.
struct RockSolid {
const float x;
const float y;
float MakeHarderConcrete() const { return x + y; }
}
Is this actually the way "we should do it" in C++? Or is there a better way?
The way you proposed is perfectly fine, except if in your code you need to make assignment of RockSolid variables, like this:
RockSolid a(0,1);
RockSolid b(0,1);
a = b;
This would not work as the copy assignment operator would have been deleted by the compiler.
So an alternative is to rewrite your struct as a class with private data members, and only public const functions.
class RockSolid {
private:
float x;
float y;
public:
RockSolid(float _x, float _y) : x(_x), y(_y) {
}
float MakeHarderConcrete() const { return x + y; }
float getX() const { return x; }
float getY() const { return y; }
}
In this way, your RockSolid objects are (pseudo-)immutables, but you are still able to make assignments.
I assume your goal is true immutability -- each object, when constructed, cannot be modified. You cannot assign one object over another.
The biggest downside to your design is that it is not compatible with move semantics, which can make functions returning such objects more practical.
As an example:
struct RockSolidLayers {
const std::vector<RockSolid> layers;
};
we can create one of these, but if we have a function to create it:
RockSolidLayers make_layers();
it must (logically) copy its contents out to the return value, or use return {} syntax to directly construct it. Outside, you either have to do:
RockSolidLayers&& layers = make_layers();
or again (logically) copy-construct. The inability to move-construct will get in the way of a number of simple ways to have optimal code.
Now, both of those copy-constructions are elided, but the more general case holds -- you cannot move your data from one named object to another, as C++ does not have a "destroy and move" operation that both takes a variable out of scope and uses it to construct something else.
And the cases where C++ will implicitly move your object (return local_variable; for example) prior to destruction are blocked by your const data members.
In a language designed around immutable data, it would know it can "move" your data despite its (logical) immutability.
One way to go about this problem is to use the heap, and store your data in std::shared_ptr<const Foo>. Now the constness is not in the member data, but rather in the variable. You can also only expose factory functions for each of your types that returns the above shared_ptr<const Foo>, blocking other construction.
Such objects can be composed, with Bar storing std::shared_ptr<const Foo> members.
A function returning a std::shared_ptr<const X> can efficiently move the data, and a local variable can have its state moved into another function once you are done with it without being able to mess with "real" data.
For a related technique, it is idomatic in less constrained C++ to take such shared_ptr<const X> and store them within a wrapper type that pretends they are not immutable. When you do a mutating operation, the shared_ptr<const X> is cloned and modified, then stored. An optimization "knows" that the shared_ptr<const X> is "really" a shared_ptr<X> (note: make sure factory functions return a shared_ptr<X> cast to a shared_ptr<const X> or this is not actually true), and when the use_count() is 1 instead casts away const and modifies it directly. This is an implementation of the technique known as "copy on write".
Now as C++ has developed, there are more opportunities for elision. Even C++23 is going to have more advanced elision. Elision is when the data isn't logically moved or copied, but just has two different names, one inside a function and one outside.
Relying on that remains awkward.
As of c++20, workarounds with getters is no longer needed.
You can now define your own copy-assignment operator for classes that contain const member objects without undefined behavior as of c++20.
This was undefined behavior prior to c++ and remains so for complete const objects but not non-const objects with const members.
https://stackoverflow.com/a/71848927/5282154

Move-assignment and reference member

Copy-assignment for a class with a reference member variable is a no-no because you can't reassign the reference. But what about move-assignment? I tried simply moveing it but, of course, that destroys the source object when I just want to move the reference itself:
class C
{
public:
C(X& x) : x_(x) {}
C(C&& other) : x_(std::move(other.x_)) {}
C& operator=(C&& other)
{
x_ = std::move(other.x_);
}
private:
X& x_;
};
X y;
C c1(y);
X z;
C c2(z);
c2 = c1; // destroys y as well as z
Should I just not be implementing move-assignment and sticking with move-construction only? That makes swap(C&, C&) hard to implement.
(Posted as an answer from comment as suggested by the OP)
In general if one wants to do non-trivial stuff with references in C++, one would be using reference_wrapper<T>, which is essentially a fancy value-semantic stand-in for a T&, then one would be done with it - it already provides (re)assignment and other operations. I'm sure that would make move constructor and assignment near-trivial, if not trivial (note not trivial as in per the is_trivially_* semantics).
"Reference wrapper" is added to C++03 as part of TR1, and is part of C++11.
Documentation: http://en.cppreference.com/w/cpp/utility/functional/reference_wrapper
A reference is, in some sense, a T *const with syntactic sugar on top to auto-derefence, auto-capture, and auto-lifetime extend temporaries. (note that this is not quite true, but often is in practice and practicality)
If you want a reseatable reference, C++ has those: they are called pointers. You can use an accessor to replace dereferencing with a function call if you like. The remaining feature (temporary lifetime extension) that is hard to emulate does not apply to struct members.