2

I like to make my C++ member variables const if they should not be changed once the object is constructed, however, sometimes they need to be modified by STL. For example, if I have a vector of my class with const members and I try to swap two elements in the vector, STL tries to use the default generated operator=() and fails because of the const member variables.

I feel like the operator=() is like a constructor in that the whole object is being created and thus would like some way to allow operator=() while still having my const member variables.

Is there anyway to do this in C++03? If not, what about in C++11, perhaps in-place construction is for this?

class Foo {
  const int _id;

  static int _generate_unique_id();

public:
  Foo()
    : _id(_generate_unique_id()) {
  }
};

vector<Foo> foo_vector;

// Fill foo_vector with several entries:
//   [...]

// Try to swap the first and second elements of the vector:
swap(*foo_vector.begin(), *(foo_vector.begin() + 1));
// The above fails to compile due to const member variable _id
// prohibits us from using the default assignment operator.
WilliamKF
  • 41,123
  • 68
  • 193
  • 295
  • 1
    How do you swap 2 elements in place whilst remaining `const`? – Alex Chamberlain Apr 26 '13 at 14:26
  • just remove the const. Since you coded the class, you can enforce the const of the member by not changing it in any functions. – Anon Mail Apr 26 '13 at 14:33
  • 1
    IMO, there's an issue trying to do that: a const object (btw one could argue that you should not make this data member public) says "I won't change my observable behaviour". Now if someone has a pointer/reference or iterator to an element in your container, and you replace the element -- the observable behaviour changes. – dyp Apr 26 '13 at 14:34
  • I'd rather say this is a problem of assigning to a object whose type has a `const` data member. This is not especially about STL or Standard Library. – dyp Apr 26 '13 at 14:36
  • 1
    @AlexChamberlain `swap(T& lhs, T& rhs) { T tmp_lhs(lhs); lhs.~T(); new(&lhs)(rhs); rhs.~T(); new(&rhs)(tmp_lhs); }` no object was modified between creation and destruction! ;) – Yakk - Adam Nevraumont Apr 26 '13 at 14:38
  • @Yakk IIRC, that is not allowed if there's a `const` data member. I'll look it up. – dyp Apr 26 '13 at 14:44
  • @Yakk: [basic.life]/7. Not allowed if there are `const` data members, sry – dyp Apr 26 '13 at 14:46
  • 2
    @yakk now, make it exception safe! – sehe Apr 26 '13 at 14:50
  • @sehe Using a `noexcept` move-ctor? – dyp Apr 26 '13 at 14:53
  • @DyP except that a container - say a `std::array` -- [has the right to destroy `const` contained objects](http://ideone.com/x0BbYu): most operations of `std::vector` can be done with destruction and copy creation. I don't think it is *wise*, but it seems doable. – Yakk - Adam Nevraumont Apr 26 '13 at 15:18
  • @Yakk what about slicing? – didierc Apr 26 '13 at 15:43
  • 1
    @didierc We have a container of `T`. And we are moving things around. Slicing is not a problem. – Yakk - Adam Nevraumont Apr 26 '13 at 15:45
  • @Yakk I'm not talking about containers of const objects, but of objects with const members. They cannot (must not, undefined behaviour) change their const members, therefore you cannot swap them completely (you can leave the const member if the previous state). – dyp Apr 26 '13 at 16:04
  • @Yakk do you mean that the use of the swap function you define can be restricted to that vector template instance? – didierc Apr 26 '13 at 20:47
  • @didierc `swap` always slices. That is what `swap` does. – Yakk - Adam Nevraumont Apr 26 '13 at 21:54

4 Answers4

0

A solution for storing not assignable objects in standard library containers is storing (smart) pointers to the objects. Not always ideal, but workable.

stefaanv
  • 14,072
  • 2
  • 31
  • 53
0

For example, if I have a vector of my class with const members and I try to swap two elements in the vector, STL tries to use the default generated operator=() and fails because of the const member variables.

Implement the "big three and a half" (default and copy constructor, assignment operator and swap), with the assignment operator explicitly skipping the reassignment if _id.

utnapistim
  • 26,809
  • 3
  • 46
  • 82
0

What you want is a thing like the Java immutable idiom. This is awesome with pointers (and thus, garbage collected languages) and less awesome with value-semantic languages like C++.

You have two solutions:

1 - Make your object immutable in the interface

The member is private (or it should be), so no one but the class itself (and its friends) can modify it. So all you need is to make sure no one does inside the class (which you control) and offer no way in the protected/public interface to leave others the power to do so.

TL;DR: Make your object non const. Don't modify it inside the class. Add a const getter. Remove the setter (if any).

2 - Use a std::unique_ptr<const Data>

Now we follow the Java idiom. The object is const, but the pointer can be reattributed, which is exactly what you want.

This is actually better than the const Data * member alternative because of its exception safety.

Bonus: Don't manually call the destructor to reconstruct again the object

There's an answer proposing that.

As mentionned first by sehe, don't do that.

Your point is to increase the quality of your code, which means your code will need to be exception safe, at one point or the other. And manually playing with your object lifetime will make it unusable in quality code.

Read Herb Sutter's article on the subject: http://www.gotw.ca/gotw/023.htm

paercebal
  • 81,378
  • 38
  • 130
  • 159
  • Thinking about this issue once again (after almost two and a half years!), I think you're on the right track mentioning value semantics as one of the underlying issues. Nowadays, I'd say that reincarnating the object is an inadequate solution; but a proper solution depends on what this `_id` member *actually means*. If it is part of the value of the object, it must not be immutable because of the guarantees of assigning/swapping `Foo`; otherwise, it can be a const data member (e.g. if it's an object id much like the address) and one has to manually implement the special member functions. – dyp Sep 08 '15 at 10:23
  • Regarding your *Bonus* paragraph: I don't think this is an issue for the OP's case of a simple `int`, but as already stated, I think this technique is inadequate because it unnecessarily makes the code harder to understand, and brittle. (So I don't think it is unusable within quality code, but can silently introduce issues e.g. when changing the type of the `_id`.) – dyp Sep 08 '15 at 10:26
-1

const on members doesn't just prevent the programmer from modifying the value of the member during its lifetime; it also enables compiler optimisations by specifying that attempts to modify it are undefined behaviour (see const member and assignment operator. How to avoid the undefined behavior?).

One way to do what you want is to write a nonmodifiable container that gives semantic const while leaving you as the programmer the possibility of modifying the contained value:

template<typename T> class nonmodifiable {
   T t;
public:
   nonmodifiable(T t): t{std::move(t)} {}
   operator const T &() const { return t; }
   nonmodifiable &operator=(const nonmodifiable &) = delete;
};

You can now write:

class Foo {
  nonmodifiable<int> _id;
  // etc.
};

and because neither _id nor its contained value are const, use the destruct-placement new dance to reassign its value:

Foo &operator=(const Foo &foo) {
   if (this != &foo) {
      _id.~nonmodifiable<int>();
      new (&_id) nonmodifiable<int>(foo._id);
   }
   return this;
}
Community
  • 1
  • 1
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • 1
    _never use the trick of implementing copy assignment in terms of copy construction by using an explicit destructor followed by placement new, even though this trick crops up every three months on the newsgroup_ [GotW #23](http://www.gotw.ca/gotw/023.htm) – sehe Apr 26 '13 at 14:53
  • @sehe many of the flaws presented there have to do with slicing / inheritance. I don't see why one would inherit from `nonmodifiable`, and certainly it's not an inherited type on which this idiom is performed here. – dyp Apr 26 '13 at 14:56
  • @dyp : Sehe is right, Herb Sutter doesn't agree (http://www.gotw.ca/gotw/023.htm) with that idiom for good reasons. The major objection is: This code is exception unsafe *by design* which is kinda ironic as the point was to increase some kind of safety. Indeed, this objectionable design choice of preventing a pathological case whose risk is limited to the class' internals has the consequence of preventing users of designing exception safe code (i.e. ALL THE CODE) outside the class. – paercebal Sep 08 '15 at 09:53
  • @dyp : `certainly it's not an inherited type on which this idiom is performed here` : I fail to see where in that "nonmodifiable" template there is a code actively preventing from using it with an heritable object, so your argument isn't valid. – paercebal Sep 08 '15 at 09:55
  • 1
    @paercebal First of all, I agree that this idiom has very limited usage. I also agree that exception safety is usually a problem with this technique, and it can easily lead to UB. However, *in this use case*, we know the placement-new won't throw, and we're not using a class derived from `nonmodifiable` at `_id`. So *this usage* is ok. It might be better to add some `static_asserts`, and make `nonmodifiable` a `final` class (but even then, one cannot assert that it doesn't contain `const` or reference data members). – dyp Sep 08 '15 at 10:07