After using std::move in a variable that might be a field in a class like:
class A {
public:
vector<string>&& stealVector() {
return std::move(myVector);
}
void recreateMyVector() {
}
private:
vector<string> myVector;
};
How would I recreate the vector, like a clear one? What is left in myVector after the std::move?
The common mantra is that a variable that has been "moved-from" is in a valid, but unspecified state. That means that it is possible to destroy and to assign to the variable, but nothing else.
(Stepanov calls this "partially formed", I believe, which is a nice term.)
To be clear, this isn't a strict rule; rather, it is a guideline on how to think about moving: After you move from something, you shouldn't want to use the original object any more. Any attempt to do something non-trivial with the original object (other than assigning to it or destroying it) should be carefully thought about and justified.
However, in each particular case, there may be additional operations that make sense on a moved-from object, and it's possible that you may want to take advantage of those. For example:
The standard library containers describe preconditions for their operations; operations with no preconditions are fine. The only useful ones that come to mind are clear(), and perhaps swap() (but prefer assignment rather than swapping). There are other operations without preconditions, such as size(), but following the above reasoning, you shouldn't have any business inquiring after the size of an object which you just said you didn't want any more.
The unique_ptr<T, D> guarantees that after being moved-from, it is null, which you can exploit in a situation where ownership is taken conditionally:
std::unique_ptr<T> resource(new T);
std::vector<std::function<int(std::unique_ptr<T> &)> handlers = /* ... */;
for (auto const & f : handlers)
{
int result = f(resource);
if (!resource) { return result; }
}
A handler looks like this:
int foo_handler(std::unique_ptr<T> & p)
{
if (some_condition))
{
another_container.remember(std::move(p));
return another_container.state();
}
return 0;
}
It would have been possible generically to have the handler return some other kind of state that indicates whether it took ownership from the unique pointer, but since the standard actually guarantees that moving-from a unique pointer leaves it as null, we can exploit that to transmit that information in the unique pointer itself.
Move the member vector to a local vector, clear the member, return the local by value.
std::vector<string> stealVector() {
auto ret = std::move(myVector);
myVector.clear();
return ret;
}
What is left in myVector after the std::move?
std::move doesn't move, it is just a cast. It can happen that myVector is intact after the call to stealVector(); see the output of the first a.show() in the example code below. (Yes, it is a silly but valid code.)
If the guts of myVector are really stolen (see b = a.stealVector(); in the example code), it will be in a valid but unspecified state. Nevertheless, it must be assignable and destructible; in case of std::vector, you can safely call clear() and swap() as well. You really should not make any other assumptions concerning the state of the vector.
How would I recreate the vector, like a clear one?
One option is to simply call clear() on it. Then you know its state for sure.
The example code:
#include <initializer_list>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class A {
public:
A(initializer_list<string> il) : myVector(il) { }
void show() {
if (myVector.empty())
cout << "(empty)";
for (const string& s : myVector)
cout << s << " ";
cout << endl;
}
vector<string>&& stealVector() {
return std::move(myVector);
}
private:
vector<string> myVector;
};
int main() {
A a({"a", "b", "c"});
a.stealVector();
a.show();
vector<string> b{"1", "2", "3"};
b = a.stealVector();
a.show();
}
This prints the followings on my machine:
a b c
(empty)
Since I feel Stepanov has been misrepresented in the answers so far, let me add a quick overview of my own:
For std types (and only those), the standard specifies that a moved-from object is left in the famous "valid, but unspecified" state. In particular, none of the std types use Stepanov's Partially-Formed State, which some, me included, think of as a mistake.
For your own types, you should strive for both the default constructor as well as the source object of a move to establish the Partially-Formed State, which Stepanov defined in Elements of Programming (2009) as a state in which the only valid operations are destruction and assignment of a new value. In particular, the Partially-Formed State need not represent a valid value of the object, nor does it need to adhere to normal class invariants.
Contrary to popular belief, this is nothing new. The Partially-Formed State exists since the dawn of C/C++:
int i; // i is Partially-Formed: only going out of scope and
// assignment are allowed, and compilers understand this!
What this practically means for the user is to never assume you can do more with a moved-from object than destroy it or assign a new value to it, unless, of course, the documentation states that you can do more, which is typically possible for containers, which can often naturally, and efficiently, establish the empty state.
For class authors, it means that you have two choices:
First, you avoid the Partially-Formed State as the STL does. But for a class with Remote State, e.g. a pimpl'ed class, this means that to represent a valid value, either you accept nullptr as a valid value for pImpl, prompting you to define, at the public API level, what a nullptr pImpl means, incl. checking for nullptr in all member functions.
Or you need to allocate a new pImpl for the moved-from (and default-constructed) object, which, of course, is nothing any performance-conscious C++ programmer would do. A performance-conscious C++ programmer, however, would also not like to litter his code with nullptr checks just to support the minor use-case of a non-trivial use of a moved-from object.
Which brings us to the second alternative: Embrace the Partially-Formed State. That means, you accept nullptr pImpl, but only for default-constructed and moved-from objects. A nullptr pImpl represents the Partially-Formed State, in which only destruction and assignment of another value are allowed. This means that only the dtor and the assignment operators need to be able to deal with a nullptr pImpl, while all other members can assume a valid pImpl. This has another benefit: both your default ctor as well as the move operators can be noexcept, which is important for use in std::vector (so moves and not copies are used upon reallocation).
Example Pen class:
class Pen {
struct Private;
Private *pImpl = nullptr;
public:
Pen() noexcept = default;
Pen(Pen &&other) noexcept : pImpl{std::exchange(other.pImpl, {})} {}
Pen(const Pen &other) : pImpl{new Private{*other.pImpl}} {} // assumes valid `other`
Pen &operator=(Pen &&other) noexcept {
Pen(std::move(other)).swap(*this);
return *this;
}
Pen &operator=(const Pen &other) {
Pen(other).swap(*this);
return *this;
}
void swap(Pen &other) noexcept {
using std::swap;
swap(pImpl, other.pImpl);
}
int width() const { return pImpl->width; }
// ...
};
Related
As mentioned here you can use reference (d-reference) instead of pointer (d-pointer) in case of PIMPL idiom.
I'm trying to understand if there are any serious issues with this implementation and what are the pros and cons.
Pros:
Shorter syntax because of usage of "." instead of "->".
...
Cons:
What if the new ObjectPivate() fails and new doesn't throw (e.g.: new(std::nothrow) or custom new) and returns nullptr instead? You need to implement additional stuff to check if the referance is valid. In case of pointer you just use:
if (m_Private)
m_Private->Foo();
In rare case of multiple constructors for the Object with complex initialisation logic the solution could be not applicable. [© JamesKanze]
It fills more natural to use pointer for memory management. [© JamesKanze]
Some additional implementation details needs to be considered (use of swap()) to ensure the exception-safety (e.g. implementation of assignment operator) [© Matt Yang]
...
Here the sample code for illustration:
// Header file
class ObjectPrivate;
class Object
{
public:
Object();
virtual ~Object();
virtual void Foo();
private:
ObjectPrivate& m_Private;
};
// Cpp file
class ObjectPrivate
{
public:
void Boo() { std::cout << "boo" << std::endl; }
};
Object::Object() :
m_Private(* new ObjectPrivate())
{
}
Object::~Object()
{
delete &m_Private;
}
void Object::Foo()
{
m_Private.Boo();
}
It's really just a matter of style. I tend to not use
references in classes to begin with, so using a pointer in the
compilation firewall just seems more natural. But there's
usually no real advantage one way or the other: the new can
only fail by means of an exception.
The one case where you might favor the pointer is when the
object has a lot of different constructors, some of which need
preliminary calculations before calling the new. In this
case, you can initialize the pointer with NULL, and then call
a common initialization routine. I think such cases are rare,
however. (I've encountered it once, that I can recall.)
EDIT:
Just another style consideration: a lot of people don't like something like delete &something;, which is needed if you use references rather than pointers. Again, it just seems more natural (to me, at least), that objects managing memory use pointers.
It's not convenient to write exception-safe code I think.
The first version of Object::operator=(Object const&) might be:
Object& operator=(Object const& other)
{
ObjectPrivate *p = &m_Private;
m_Private = other.m_Private; // Dangerous sometimes
delete *p;
}
It's dangerous if ObjectPrivate::operator=(ObjectPrivate const&) throws exception. Then what about using a temporary variable? Aha, no way. operator=() has to be invoked if you want change m_Private.
So, void ObjectPrivate::swap(ObjectPrivate&) noexcept can act as our savior.
Object& operator=(Object const& other)
{
ObjectPrivate *tmp = new ObjectPrivate(other.m_Private);
m_Private.swap(*tmp); // Well, no exception.
delete tmp;
}
Then consider the implementation of void ObjectPrivate::swap(ObjectPrivate&) noexcept. Let's assume that ObjectPrivate might contain a class instance without swap() noexcept or operator=() noexcept. I think it's hard.
Alright then, this assumption is too strict and not correct sometimes. Even so, it's not necessary for ObjectPrivate to provide swap() noexcept in most cases, because it's usually a helper structure to centralize data.
By contrast, pointer can save a lot of brain cells.
Object& operator=(Object const& other)
{
ObjectPrivate *tmp = new ObjectPrivate(*other.p_Private);
delete p_Private;
p_Private = tmp; // noexcept ensured
}
It's much more elegant if smart pointers are used.
Object& operator=(Object const& other)
{
p_Private.reset(new ObjectPrivate(*other.p_Private));
}
Some quick and obvious additions:
Pro
The reference must not be 0.
The reference may not be assigned another instance.
Class responsibilities/implementation are simpler due to fewer variables.
The compiler could make some optimizations.
Con
The reference may not be assigned another instance.
The reference will be too restrictive for some cases.
I have class like this
class variable
{
public:
variable(int _type=0) : type(_type), value(NULL), on_pop(NULL)
{
}
virtual ~variable()
{
if (type)
{
std::cout << "Variable Deleted" <<std::endl;
on_pop(*this);
value=NULL;
}
}
int type;
void* value;
typedef void(*func1)(variable&);
func1 on_pop;
}
And then I push instances into a std::vector like this:
stack.push_back(variable(0));
I expect that the destructor of variable will be called but the if won't enter until a value is assigned to type because I expect the constructor I provide will be called when the instance is copied into the vector. But for some reason it is not.
After calling stack.push_back the destructor (of the copy?) is ran and type has some random value like if the constructor was never called.
I can't seem to figure what I am doing wrong. Please help! ^_^
EDIT:
Ok here is a self contained example to show what I mean:
#include <iostream>
#include <vector>
class variable
{
public:
variable(int _type=0) : type(_type), value(NULL), on_pop(NULL)
{
}
~variable()
{
if (type)
{
std::cout << "Variable Deleted" <<std::endl;
on_pop(*this);
value=NULL;
}
}
int type;
void* value;
typedef void(*func1)(variable&);
func1 on_pop;
};
static void pop_int(variable& var)
{
delete (int*)var.value;
}
static void push_int(variable& var)
{
var.type = 1;
var.value = new int;
var.on_pop = &pop_int;
}
typedef void(*func1)(variable&);
func1 push = &push_int;
int main()
{
std::vector<variable> stack;
stack.push_back(variable(0));
push(stack[stack.size()-1]);
stack.push_back(variable(0));
push(stack[stack.size()-1]);
stack.push_back(variable(0));
push(stack[stack.size()-1]);
return 0;
}
The program above outputs the following:
Variable Deleted
Variable Deleted
Variable Deleted
Variable Deleted
Variable Deleted
Variable Deleted
Process returned 0 (0x0) execution time : 0.602 s
Press any key to continue.
Welcome to RVO and NRVO. This basically means that the compiler can skip creating an object if it's redundant- even if it's constructor and destructor have side effects. You cannot depend on an object which is immediately copied or moved to actually exist.
Edit: The actual value in the vector cannot be ellided at all. Only the intermediate variable variable(0) can be ellided. The object in the vector must still be constructed and destructed as usual. These rules only apply to temporaries.
Edit: Why are you writing your own resource management class? You could simply use unique_ptr with a custom deleter. And your own RTTI?
Every object that was destructed must have been constructed. There is no rule in the Standard that violates this. RVO and NRVO only become problematic when you start, e.g., modifying globals in your constructors/destructors. Else, they have no impact on the correctness of the program. That's why they're Standard. You must be doing something else wrong.
Ultimately, I'm just not sure exactly WTF is happening to you and why it's not working or what "working" should be. Post an SSCCE.
Edit: In light of your SSCCE, then absolutely nothing is going wrong whatsoever. This is entirely expected behaviour. You have not respected the Rule of Three- that is, you destroy the resource in your destructor but make no efforts to ensure that you actually own the resource in question. Your compiler-generated copy constructor is blowing up your logic. You must read about the Rule of Three, copy and swap and similar idioms for resource handling in C++, and preferably, use a smart pointer which is already provided as Standard like unique_ptr which does not have these problems.
After all, you create six instances of variable- three temporaries on the stack, and three inside the vector. All of these have their destructors called. The problem is that you never considered the copy operation or what copying would do or what would happen to these temporaries (hint: they get destructed).
Consider the equal example of
int main()
{
variable v(0);
push_int(v);
variable v2 = v;
return 0;
}
Variable v is constructed and allocates a new int and everything is dandy. But wait- then we copy it into v2. The compiler-generated constructor copies all the bits over. Then both v2 and v are destroyed- but they both point to the same resource because they both hold the same pointer. Double delete abounds.
You must define copy (shared ownership - std::shared_ptr) or move (unique ownership - std::unique_ptr) semantics.
Edit: Just a quick note. I observe that you actually don't push into items until after they're already in the vector. However, the same effect is observed when the vector must resize when you add additional elements and the fundamental cause is the same.
The destructor is called 6 times. A constructor is called six times. Just not the one you intended.
Ok. I've been reading some more about the intrinsics of different containers and, apparently, the one that does the job I'm trying to accomplish here is std::deque.
When implementing move constructors and move assignment operators, one often writes code like this:
p = other.p;
other.p = 0;
The implicitly defined move operations would be implemented with code like this:
p = std::move(other.p);
Which would be wrong, because moving a pointer variable does not set it to null. Why is that? Are there any cases were we would like the move operations to leave the original pointer variable unchanged?
Note: By "moving", I do not just mean the subexpression std::move(other.p), I mean the whole expression p = std::move(other.p). So, why is there no special language rule that says "If the right hand side of an assignment is a pointer xvalue, it is set to null after the assignment has taken place."?
Setting a raw pointer to null after moving it implies that the pointer represents ownership. However, lots of pointers are used to represent relationships. Moreover, for a long time it is recommended that ownership relations are represented differently than using a raw pointer. For example, the ownership relation you are referring to is represented by std::unique_ptr<T>. If you want the implicitly generated move operations take care of your ownership all you need to do is to use members which actually represent (and implement) the desired ownership behavior.
Also, the behavior of the generated move operations is consistent with what was done with the copy operations: they also don't make any ownership assumptions and don't do e.g. a deep copy if a pointer is copied. If you want this to happen you also need to create a suitable class encoding the relevant semantics.
Moving renders the moved-from object "invalid". It does not automatically set it to a safe "empty" state. In accordance with C++'s long-standing principle of "don't pay for what you don't use", that's your job if you want it.
I think the answer is : implementing such a behavior yourself is pretty much trivial and hence the Standard didn't feel any need to impose any rule on the compiler itself. The C++ language is huge and not everything can be imagined before its use. Take for example, C++'s template. It was not first designed to be used the way it is used today (i.e it's metaprogramming capability). So I think, the Standard just gives the freedom, and didn't make any specific rule for std::move(other.p), following one of it's the design-principle: "You don't pay for what you don't use".
Although, std::unique_ptr is movable, though not copyable. So if you want pointer-semantic which is movable and copyable both, then here is one trivial implementation:
template<typename T>
struct movable_ptr
{
T *pointer;
movable_ptr(T *ptr=0) : pointer(ptr) {}
movable_ptr<T>& operator=(T *ptr) { pointer = ptr; return *this; }
movable_ptr(movable_ptr<T> && other)
{
pointer = other.pointer;
other.pointer = 0;
}
movable_ptr<T>& operator=(movable_ptr<T> && other)
{
pointer = other.pointer;
other.pointer = 0;
return *this;
}
T* operator->() const { return pointer; }
T& operator*() const { return *pointer; }
movable_ptr(movable_ptr<T> const & other) = default;
movable_ptr<T> & operator=(movable_ptr<T> const & other) = default;
};
Now you can write classes, without writing your own move-semantics:
struct T
{
movable_ptr<A> aptr;
movable_ptr<B> bptr;
//...
//and now you could simply say
T(T&&) = default;
T& operator=(T&&) = default;
};
Note that you still have to write copy-semantics and the destructor, as movable_ptr is not smart pointer.
For example, if you have a pointer to a shared object. Remember, that after moving an object must remain in an internally consistent state, so setting a pointer that must not be null to a null value is not correct.
I.e.:
struct foo
{
bar* shared_factory; // This can be the same for several 'foo's
// and must never null.
};
Edit
Here is a quote about MoveConstructibe from the standard:
T u = rv;
...
rv’s state is unspecified [ Note:rv must still meet the requirements
of the library component that is using it. The operations listed in
those requirements must work as specified whether rv has been moved
from or not.
I think what makes the difference here is a fully blown object on the one hand and a POD on the other hand.
For objects either the implementer specifies what move construction and move assignment should do or the compiler generates a default. The default is to call the move constructors/assignment operators of all member.
For POD's (and a pointer is a POD) C++ inherits from C and nothing is done with it when not explicitely coded. It's just the same behavior as POD members in a class are treated in a constructor. If you don't explicitely put them in the initializer list, well, then they stay uninitialized and are a source of potential bugs. AFAIK this is even valid for compiler generated constructors! That's why I took on the habit of generally initializing all members to be on the safe side.
I'm looking for the best-practice of dealing with non-copyable objects.
I have a mutex class, that obviously should not be copyable.
I added a private copy constructor to enforce that.
That broke the code - some places simply needed to be fixed, but I have a generic problem
where a class, using the mutex either as a data member, or by inheritance, is being inserted into a container.
This is usually happening during the container initialization, so the mutex is not initialized yet, and is therefore ok, but without a copy constructor it does not work. Changing the containers to contain pointers is not acceptable.
Any advise?
Three solutions here:
1. Use Pointers - The quick fix is to make it a container of pointers - e.g. a shared_ptr.
That would be the "good" solution if your objects are truly noncopyable, and using other containers is not possible.
2. Other containers - Alternatively, you could use non-copying containers (that use in-place-construction), however they aren't very common and largely incompatible with STL. (I've tried for a while, but it's simply no good)
That would be the "god" solution if your objects are truly noncopyable, and using pointers is not possible.
[edit] With C++13, std::vector allows inplace construction (emplace_back), and can be used for noncopyable objects that do implement move semantics.
[/edit]
3. Fix your copyability - If your class is copyable as such, and the mutex is not, you "simply" need to fix the copy constructor and assignment operator.
Writing them is a pain, since you usually have to copy & assign all members except the mutex, but that can often be simplified by:
template <typename TNonCopyable>
struct NeverCopy : public T
{
NeverCopy() {}
NeverCopy(T const & rhs) {}
NeverCopy<T> & operator=(T const & rhs) { return *this; }
}
And changing you mutex member to
NeverCopy<Mutex> m_mutex;
Unfortunately, using that template you lose special constructors of Mutex.
[edit] Warning: "Fixing" the Copy CTor/asignment often requires you to lock the right hand side on copy construct, and lock both sides on assignment. Unfortunately, there is no way to override the copy ctor/assignment and call the default implementation, so the NeverCopy trick might not work for you without external locking. (There are some other workarounds with their own limitations.)
If they are non-copyable, the container has to store (smart) pointers to those objects, or reference wrappers, etc, although with C++0x, noncopyable objects can still be movable (like boost threads), so that they can be stored in containers as-is.
to give examples: reference wrapper (aka boost::ref, a pointer under the hood)
#include <vector>
#include <tr1/functional>
struct Noncopy {
private:
Noncopy(const Noncopy&) {}
public:
Noncopy() {}
};
int main()
{
std::vector<std::tr1::reference_wrapper<Noncopy> > v;
Noncopy m;
v.push_back(std::tr1::reference_wrapper<Noncopy>(m));
}
C++0x, tested with gcc:
#include <vector>
struct Movable {
private:
Movable(const Movable&) = delete;
public:
Movable() {}
Movable(Movable&&) {}
};
int main()
{
std::vector<Movable> v;
Movable m;
v.emplace_back(std::move(m));
}
EDIT: Nevermind, C++0x FCD says, under 30.4.1/3,
A Mutex type shall not be copyable nor movable.
So you're better off with pointers to them. Smart or otherwise wrapped as necessary.
If an object is non-copyable then there is usually a good reason. And if there's a good reason then you shouldnt subvert that by putting it into a container that attempts to copy it.
There is no real answer to the question given how you've framed it. There is no way to do what you want. The actual answer is for the container to contain pointers, and you've said that isn't OK for some unspecified reason.
Some have talked about things being movable and using C++0x in which containers often require their elements to be movable, but do not require them to be copyable. I think this is a poor solution as well because I suspect that mutexes should not be moved while they are held, and this makes it effectively impossible to move them.
So, the only real remaining answer is to point at the mutexes. Use ::std::tr1::shared_ptr (in #include <tr1/memory>) or ::boost::shared_ptr to point at the mutexes. This requires changing the definitions of the classes that have the mutexes inside them, but it sounds like you're doing that anyway.
STL containers rely heavily on their contents being copyable, so either make them copyable or do not put them into container.
The best option is to use pointers or some type of wrapper class that uses pointers under the hood. That would allow these to be sanely copied, and actually do what a copy would be expected to do (share the mutex).
But, since you said no pointers, there is one other option. It sounds like Mutexes are "sometimes copyable" perhaps you should write a copy constructor and an assignment operator and have those throw an exception if a mutex is ever copied after it has been initialized. The down side is there's no way to know you're doing it wrong until runtime.
Use smart pointers like boost::shared_ptr or use another containers, like boost::intrusive. Both will require to modify your code, through.
Using a mutex in a class does not necessarily mean that the class has to be non-copyable. You can (almost) always implement it like this:
C::C (C const & c)
// No ctor-initializer here.
{
MutexLock guard (c.mutex);
// Do the copy-construction here.
x = c.x;
}
While this makes it somewhat possible to copy classes with mutexes, you probably should not do it. Chances are that your design will be better without per-instance mutex.
Using c++11 on Ubuntu 14.04 (which includes emplace_back), I've gotten this to work.
I found that emplace_back worked fine, but erase (and probably insert) didn't work because, when the vector was shuffling the elements along to fill in the gap, it mas using:
*previous = *current;
I found the trick was allowing move assignment in my resource class:
Watch& operator=(Watch &&other);
This is my inotify_watch class, which can live in a std::vector:
class Watch {
private:
int inotify_handle = 0;
int handle = -1;
// Erases all knowledge of our resources
void erase() {
inotify_handle = 0;
handle = -1;
}
public:
Watch(int inotify_handle, const char *path, uint32_t mask)
: inotify_handle(inotify_handle),
handle(inotify_add_watch(inotify_handle, path, mask)) {
if (handle == -1)
throw std::system_error(errno, std::system_category());
}
Watch(const Watch& other) = delete; // Can't copy it, it's a real resource
// Move is fine
Watch(Watch &&other)
: inotify_handle(other.inotify_handle), handle(other.handle) {
other.erase(); // Make the other one forget about our resources, so that
// when the destructor is called, it won't try to free them,
// as we own them now
}
// Move assignment is fine
Watch &operator=(Watch &&other) {
inotify_handle = other.inotify_handle;
handle = other.handle;
other.erase(); // Make the other one forget about our resources, so that
// when the destructor is called, it won't try to free them,
// as we own them now
return *this;
}
bool operator ==(const Watch& other) {
return (inotify_handle == other.inotify_handle) && (handle == other.handle);
}
~Watch() {
if (handle != -1) {
int result = inotify_rm_watch(inotify_handle, handle);
if (result == -1)
throw std::system_error(errno, std::system_category());
}
}
};
std::vector can not store non-copyable objects (due to resize) thus you can't store objects of type Foo:
struct Foo {
std::mutex mutex;
...
};
One way around this is to use std::unique_ptr:
struct Foo {
std::unique_ptr<std::mutex> pmutex;
Foo() : pmutex{std::make_unique<std::mutex>()} {}
...
};
Another option is to use a std::deque which can hold non-copyable objects (like instances of the first version of Foo above. Typically use emplace_back method to construct objects "in place" to add elements -- no copies happen. For example,
objects of type Foo here do not need to be copiable:
struct FooPool {
std::deque<Foo> objects;
ObjectPool(std::initializer_list<T> argList) {
for (auto&& arg : argList)
objects.emplace_back(arg);
...
};
Given a class like this:
class Foo
{
const int a;
};
Is it possible to put that class in a vector? When I try, my compiler tells me it can't use the default assignment operator. I try to write my own, but googling around tells me that it's impossible to write an assignment operator for a class with const data members. One post I found said that "if you made [the data member] const that means you don't want assignment to happen in the first place." This makes sense. I've written a class with const data members, and I never intended on using assignment on it, but apparently I need assignment to put it in a vector. Is there a way around this that still preserves const-correctness?
I've written a class with const data members, and I never intended on using assignment on it, but apparently I need assignment to put it in a vector. Is there a way around this that still preserves const-correctness?
You have to ask whether the following constraint still holds
a = b;
/* a is now equivalent to b */
If this constraint is not true for a and b being of type Foo (you have to define the semantics of what "equivalent" means!), then you just cannot put Foo into a Standard container. For example, auto_ptr cannot be put into Standard containers because it violates that requirement.
If you can say about your type that it satisfies this constraint (for example if the const member does not in any way participate to the value of your object, but then consider making it a static data member anyway), then you can write your own assignment operator
class Foo
{
const int a;
public:
Foo &operator=(Foo const& f) {
/* don't assign to "a" */
return *this;
}
};
But think twice!. To me, it looks like that your type does not satisfy the constraint!
Use a vector of pointers std::vector<Foo *>. If you want to avoid the hassle of cleaning up after yourself, use boost::ptr_vector.
Edit: My initial stab during my coffee break, static const int a; won't work for the use case the OP has in mind, which the initial comments confirm, so I'm rewriting and expanding my answer.
Most of the time, when I want to make an element of a class constant, it's a constant whose value is constant for all time and across all instances of the class. In that case, I use a static const variable:
class Foo
{
public:
static const int a;
};
Those don't need to be copied among instances, so if it applied, that would fix your assignment problem. Unfortunately, the OP has indicated that this won't work for the case the OP has in mind.
If you want to create a read-only value that clients can't modify, you can make it a private member variable and only expose it via a const getter method, as another post on this thread indicates:
class Foo
{
public:
int get_a() const { return a; }
private:
int a;
};
The difference between this and
class Foo
{
public:
const int a;
};
is:
The const int gives you assurance that not even the implementation of the class will be able to muck with the value of a during the lifetime of the object. This means that assignment rightfully won't work, since that would be trying to modify the value of a after the object's been created. (This is why, btw, writing a custom operator=() that skips the copy of a is probably a bad idea design-wise.)
The access is different – you have to go through a getter rather than accessing the member directly.
In practice, when choosing between the two, I use read-only members. Doing so probably means you'll be able to replace the value of an object with the value of another object without violating semantics at all. Let's see how it would work in your case.
Consider your Grid object, with a width and height. When you initially create the vector, and let's say you reserve some initial space using vector::reserve(), your vector will be populated with initial default-initialized (i.e. empty) Grids. When you go to assign to a particular position in the vector, or push a Grid onto the end of the vector, you replace the value of the object at that position with a Grid that has actual stuff. But you may be OK with this! If the reason you wanted width and height to be constant is really to ensure consistency between width and height and the rest of the contents of your Grid object, and you've verified that it doesn't matter whether width and height are replaced before or after other elements of Grid are replaced, then this assignment should be safe because by the end of the assignment, the entire contents of the instance will have been replaced and you'll be back in a consistent state. (If the lack of atomicity of the default assignment was a problem, you could probably get around this by implementing your own assignment operator which used a copy constructor and a swap() operation.)
In summary, what you gain by using read-only getters is the ability to use the objects in a vector or any container with value semantics. However, it then falls to you to ensure that none of Grid's internal operations (or the operations of friends of Grid) violate this consistency, because the compiler won't be locking down the width and height for you. This goes for default construction, copy construction, and assignment as well.
I'm considering making the data member non-const, but private and only accessible by a get function, like this:
class Foo
{
private:
int a;
public:
int getA() const {return a;}
};
Is this 'as good' as const? Does it have any disadvantages?
As of c++20, using const member variables are legal without restrictions that had made it virtually unusable in containers. You still have to define a copy assignment member function because it continues to be automatically deleted when a const object exists in the class. However, changes to "basic.life" now allow changing const sub-objects and c++ provides rather convenient functions for doing this. Here's a description of why the change was made:
The following code shows how to define a copy assignment member function which is useable in any class containing const member objects and uses the new functions std::destroy_at and std::construct_at to fulfil the requirement so the new "basic.life" rules. The code demonstrates assignment of vectors as well as sorting vectors with const elements.
Compiler explorer using MSVC, GCC, CLANG https://godbolt.org/z/McfcaMWqj
#include <memory>
#include <vector>
#include <iostream>
#include <algorithm>
class Foo
{
public:
const int a;
Foo& operator=(const Foo& arg) {
if (this != &arg)
{
std::destroy_at(this);
std::construct_at(this, arg);
}
return *this;
}
};
int main()
{
std::vector<Foo> v;
v.push_back({ 2 });
v.push_back({ 1 });
v.insert(v.begin() + 1, Foo{ 0 });
std::vector<Foo> v2;
v2 = v;
std::sort(v2.begin(), v2.end(), [](auto p1, auto p2) {return p1.a < p2.a; });
for (auto& x : v2)
std::cout << x.a << '\n';
}