Why isn't observer_ptr zeroed after a move? - c++

Why isn't the observer_ptr zeroed after a move operation?
It is correctly set to nullptr in its default construction, and that does make sense (and prevents pointing to garbage).
And, accordingly, it should be zeroed when std::move()'d from, just like std::string, std::vector, etc.
This would make it a good candidate in several contexts where raw pointers make sense, as well as automatic generation of move operations on classes with raw pointer data members, like in this case.
EDIT
As #JonathanWakely pointed out in the comments (and that is related to the aforementioned question):
if observer_ptr was null after a move it can be used to implement
the Rule of Zero for types that have a pointer member. It's a very
useful feature.

It seems like many people miss the point and the utility of this idea at first.
Consider:
template<typename Mutex>
class unique_lock
{
Mutex* pm;
public:
unique_lock() : pm() { }
unique_lock(Mutex& m) : pm(&m) { }
~unique_lock() { if (pm) pm->unlock(); }
unique_lock(unique_lock&& ul) : pm(ul.pm) { ul.pm = nullptr; }
unique_lock& operator=(unique_lock&& ul)
{
unique_lock(std::move(ul)).swap(*this);
return *this;
}
void swap(unique_lock& ul) { std::swap(pm, ul.pm); }
};
With a "dumb" smart pointer that is null-on-default-construction and null-after-move you can default three of the special member functions, so it becomes:
template<typename Mutex>
class unique_lock
{
tidy_ptr<Mutex> pm;
public:
unique_lock() = default; // 1
unique_lock(Mutex& m) : pm(&m) { }
~unique_lock() { if (pm) pm->unlock(); }
unique_lock(unique_lock&& ul) = default; // 2
unique_lock& operator=(unique_lock&& ul) = default; // 3
void swap(unique_lock& ul) { std::swap(pm, ul.pm); }
};
That's why it's useful to have a dumb, non-owning smart pointer that is null-after-move, like tidy_ptr
But observer_ptr is only null-on-default-construction, so if it is standardized it will be useful for declaring a function to take a non-owning pointer, but it won't be useful for classes like the one above, so I'll still need another non-owning dumb pointer type. Having two non-owning dumb smart pointer types seems almost worse than having none!

So, move constructors are designed to make copy constructors cheaper in certain cases.
Let's write out what we'd expect these constructors and destructors to be: (This is a bit of a simplification, but that's fine for this example).
observer_ptr() {
this->ptr == nullptr;
}
observer_ptr(T *obj) {
this->ptr = obj;
}
observer_ptr(observer_ptr<T> const & obj) {
this->ptr = obj.ptr;
}
~observer_ptr() {
}
You're suggesting that the class provides a move constructor that looks like:
observer_ptr(observer_ptr<T> && obj) {
this->ptr = obj.ptr;
obj.ptr = null;
}
When I would suggest that the existing copy constructor will work fine as is, and is cheaper than the suggested move constructor.
What about std::vector though?
A std::vector, when copied, actually copies the array that it backs. So, a std::vector copy constructor looks something like:
vector(vector<T> const & obj) {
for (auto const & elem : obj)
this->push_back(elem);
}
The move constructor for the std::vector can optimize this. It can do this because that memory can be stolen from obj. In a std::vector, this is actually useful to do.
vector(vector<T> && obj) {
this->data_ptr = obj.data_ptr;
obj.data_ptr = nullptr;
obj.size = 0;
}

Aside from the minor performance impact of zeroing the moved-from observer_ptr, which is easily worked around (copy instead of move), the main rationale was probably to mimic the behaviour of regular pointers as closely as possible, following the principle of least surprise.
However, there is a potentially far more significant performance issue: defaulting the move functions allows an observer_ptr to be trivially copyable. Trivial copyability allows an object to be copied using std::memcpy. If observer_ptr were not trivially copyable, neither would any classes with an observer_ptr data member, resulting in a performance penalty that cascades down the compositional class hierarchy.
I have no idea what kind of performance improvements can be gained by using the std::memcpy optimization, but they're probably more significant than the aforementioned minor performance issue.

Related

What lasts after using std::move c++11

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 pre­conditions 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 indi­cates whether it took ownership from the unique pointer, but since the standard actually guaran­tees 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; }
// ...
};

Pros and Cons of usage of reference in case of PIMPL idiom

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.

Should a type be move-only, just because copying may be expensive?

I have a type that is copyable, but may be expensive to copy. I have implemented the move constructor and move assignment. But I have performance issues where folks forget to call move() when passing by value.
Is it good C++11 style to remove the copy constructor, and instead provide an explicit copy() method for the rare cases when a copy is actually desired? This is idiomatic in other languages (Ruby, JavaScript) but I don't know of anything in the C++ standard library that prohibits copy purely for performance. For instance, std::vector<> is copyable, while std::unique_ptr<> and std::thread are non copyable for other reasons.
Should a type be move-only, just because copying may be expensive?
No. If the semantics of your type is such that copying it is conceptually meaningful, then the correct way to make copying available is to implement a copy constructor, and give the user a chance to adopt standard syntax for invoking it:
T a;
T a = b;
If people will forget to move from objects they don't want to use anymore... Well, that's their bad:
T c = std::move(a); // I'm doing it right (if I no longer need object a);
T d = b; // If I don't need b anymore, I'm doing it wrong.
And if (for any reason) for some functions of yours it is always desirable that the caller provides an object from which it is possible to move, then let the function accept an rvalue reference:
void foo(my_class&& obj);
my_class a;
foo(a); // ERROR!
foo(std::move(a)); // OK
I would treat the class as non-copyable in signature if copy is sufficiently expensive. Semantically things are copyable only if you want them to be, and an expensive copy is a decent reason to decide "no, not copyable".
The ability for something to be copied does not mean it need be implemented in a type that is copyable. The implementer of that type gets to decide if it should be semantically copyable.
I wouldn't call the operation that produced an expensive copy "copy", but rather "clone" or "duplicate".
For a way you might do this:
#include <utility>
template<typename T>
struct DoCopy {
T const& t;
DoCopy( T const& t_ ):t(t_) {}
};
template<typename T>
DoCopy<T> do_copy( T const& t ) {
return t;
}
struct Foo {
struct ExpensiveToCopy {
int _[100000000];
};
ExpensiveToCopy* data;
Foo():data(new ExpensiveToCopy()) {}
~Foo(){ delete data; }
Foo(Foo&& o):data(o.data) { o.data = nullptr; }
Foo& operator=(Foo&& o) { data=o.data; o.data=nullptr; return *this; }
Foo& operator=(DoCopy<Foo> o) {
delete data;
if (o.t.data) {
data=new ExpensiveToCopy(*o.t.data);
} else {
data=new ExpensiveToCopy();
}
return *this;
}
Foo( DoCopy<Foo> cp ):data(cp.t.data?new ExpensiveToCopy( *cp.t.data ):new ExpensiveToCopy() ) {};
};
int main() {
Foo one;
// Foo two = one; // illegal
Foo three = std::move(one); // legal
Foo four;
Foo five = do_copy(three);
four = std::move(three);
five = do_copy(four);
}
This is somewhat similar to the ways you could have written std::move like semantics prior to the existence of rvalue references, with similar downsides to such techniques, namely that the language itself has no idea what shenanigans you are up to.
It has the advantage that the syntax of the above do_copy is similar to the syntax of std::move, and it allows you to use traditional expressions without having to create trivial instances of Foo then construct a copy of another variable etc.
If the situations where we want to treat it as copyable are common (if to be avoided), I'd write a copy-wrapper around the class that knows about the duplicate method.
No. If the type is copyable then the type is copyable. This means its copy constructor is available and works. It doesn't mean there's some member function whose name looks like the characters c, o, p and y in sequence, that does "sort of nearly a similar thing".

How to deal with noncopyable objects when inserting to containers in C++

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);
...
};

Allow member to be const while still supporting operator= on the class

I have several members in my class which are const and can therefore only be initialised via the initialiser list like so:
class MyItemT
{
public:
MyItemT(const MyPacketT& aMyPacket, const MyInfoT& aMyInfo)
: mMyPacket(aMyPacket),
mMyInfo(aMyInfo)
{
}
private:
const MyPacketT mMyPacket;
const MyInfoT mMyInfo;
};
My class can be used in some of our internally defined container classes (e.g. vectors), and these containers require that operator= is defined in the class.
Of course, my operator= needs to do something like this:
MyItemT&
MyItemT::operator=(const MyItemT& other)
{
mMyPacket = other.mPacket;
mMyInfo = other.mMyInfo;
return *this;
}
which of course doesn't work because mMyPacket and mMyInfo are const members.
Other than making these members non-const (which I don't want to do), any ideas about how I could fix this?
You're kind of violating the definition of const if you have an assignment operator that can change them after construction has finished. If you really need to, I think Potatoswatter's placement new method is probably best, but if you have an assignment operator your variables aren't really const, since someone could just make a new instance and use it to change their values
Rather than storing objects in your containers directly, you might be able to store pointers (or smart pointers). That way, you don't have to mutate any of the members of your class -- you get back exactly the same object as you passed in, const and all.
Of course, doing this will probably change the memory management of your application somewhat, which may well be a good enough reason not to want to.
It's a dirty hack, but you can destroy and reconstruct yourself:
MyItemT&
MyItemT::operator=(const MyItemT& other)
{
if ( this == &other ) return *this; // "suggested" by Herb Sutter ;v)
this->MyItemT::~MyItemT();
try {
new( this ) MyItemT( other );
} catch ( ... ) {
new( this ) MyItemT(); // nothrow
throw;
}
return *this;
}
Edit: lest I destroy my credibility, I don't actually do this myself, I would remove the const. However, I've been debating changing the practice, because const simply is useful and better to use wherever possible.
Sometimes there is a distinction between the resource and the value represented by an object. A member may be const through changes to value as long as the resource is the same, and it would be nice to get compile-time safety on that.
Edit 2: #Charles Bailey has provided this wonderful (and highly critical) link: http://gotw.ca/gotw/023.htm.
Semantics are tricky in any derived class operator=.
It may be inefficient because it doesn't invoke assignment operators that have been defined.
It's incompatible with wonky operator& overloads (whatever)
etc.
Edit 3: Thinking through the "which resource" vs "what value" distinction, it seems clear that operator= should always change the value and not the resource. The resource identifier may then be const. In the example, all the members are const. If the "info" is what's stored inside the "packet," then maybe the packet should be const and the info not.
So the problem isn't so much figuring out the semantics of assignment as lack of an obvious value in this example, if the "info" is actually metadata. If whatever class owns a MyItemT wants to switch it from one packet to another, it needs to either give up and use an auto_ptr<MyItemT> instead, or resort to a similar hack as above (the identity test being unnecessary but the catch remaining) implemented from outside. But operator= shouldn't change resource binding except as an extra-special feature which absolutely won't interfere with anything else.
Note that this convention plays well with Sutter's advice to implement copy construction in terms of assignment.
MyItemT::MyItemT( MyItemT const &in )
: mMyPacket( in.mMyPacket ) // initialize resource, const member
{ *this = in; } // assign value, non-const, via sole assignment method
I think you could get away with a special const proxy.
template <class T>
class Const
{
public:
// Optimal way of returning, by value for built-in and by const& for user types
typedef boost::call_traits<T>::const_reference const_reference;
typedef boost::call_traits<T>::param_type param_type;
Const(): mData() {}
Const(param_type data): mData(data) {}
Const(const Const& rhs): mData(rhs.mData) {}
operator const_reference() const { return mData; }
void reset(param_type data) { mData = data; } // explicit
private:
Const& operator=(const Const&); // deactivated
T mData;
};
Now, instead of const MyPacketT you would have Const<MyPacketT>. Not that the interface only provides one way to change it: through an explicit call to reset.
I think any use of mMyPacket.reset can easily be search for. As #MSalters said it protects against Murphy, not Machiavelli :)
You might consider making the MyPacketT and MyInfoT members be pointers to const (or smart pointers to const). This way the data itself is still marked const and immutable, but you can cleanly 'swap' to another set of const data in an assignment if that makes sense. In fact, you can use the swap idiom to perform the assignment in an exception safe manner.
So you get the benefit of const to help you prevent accidentally allowing changes that you want the design to prevent, but you still allow the object as a whole to be assigned from another object. For example, this will let you use objects of this class in STL containers.
You might look at this as a special application of the 'pimpl' idiom.
Something along the lines of:
#include <algorithm> // for std::swap
#include "boost/scoped_ptr.hpp"
using namespace boost;
class MyPacketT {};
class MyInfoT {};
class MyItemT
{
public:
MyItemT(const MyPacketT& aMyPacket, const MyInfoT& aMyInfo)
: pMyPacket(new MyPacketT( aMyPacket)),
pMyInfo(new MyInfoT( aMyInfo))
{
}
MyItemT( MyItemT const& other)
: pMyPacket(new MyPacketT( *(other.pMyPacket))),
pMyInfo(new MyInfoT( *(other.pMyInfo)))
{
}
void swap( MyItemT& other)
{
pMyPacket.swap( other.pMyPacket);
pMyInfo.swap( other.pMyInfo);
}
MyItemT const& operator=( MyItemT const& rhs)
{
MyItemT tmp( rhs);
swap( tmp);
return *this;
}
private:
scoped_ptr<MyPacketT const> pMyPacket;
scoped_ptr<MyInfoT const> pMyInfo;
};
Finally, I changed my example to use scoped_ptr<> instead of shared_ptr<> because I thought it was a more general representation of what the OP intended. However, if the 'reassignable' const members can be shared (and that's probably true, given my understanding of why the OP wants them const), then it might be an optimization to use shared_ptr<>'s and let the copy and assignment operations of the shared_ptr<> class take care of things for those objects - if you have no other members that require special copy or assign sematics, then your class just got a lot simpler, and you might even save a significant bit of memory usage by being able to share copies of the MyPacketT and MyInfoT objects.