About 11 months ago I started this thread asking why I couldn't use a const data member in a class that was going to be used in an stl container. The explanations were excellent.

Now my question is, what is the best alternative to having a class with BOTH
a) const data members and
b) an assignment operator (necessary for stl containers)?

As far as I can tell, the main reason to declare a data member as const is to remind the user of that class not to change it's value.

My current solution is to use a pointer to a const object that lives in a more global scope. While the pointer itself is not constant, at least the underlying object is const. Now I'm wondering if there is a better way.

I've found threads that used casting in the assignment operator to copy const data members (seems like a bad idea), and yet another that suggested avoiding the assignemt operator all together by using vector<shared_ptr<myObj>> instead of vector<myObj> for instance (seems like a pretty good idea).

Which would generally be the best solution or is it a case by case kind of thing?

Recommended Answers

All 2 Replies

As far as I can tell, the main reason to declare a data member as const is to remind the user of that class not to change it's value.

A const member variable is an object that is initialized during construction of the object and is never changed after that. The const member is part of the logical state of an object, and this state never changes during the life of an object.

My current solution is to use a pointer to a const object that lives in a more global scope.

This is fine if and only if the above is not true.

For example, let us say that we have a class that represents an object with a persistent external state; and let us assume (without losing generality) that the persistent state is stored in a seperate file for each object. Say, we have a directory in which we have files file<nnnn>.txt where nnnn is a number 0001 to 9999, used as a unique id for each object. In this case, we can assign to everything else except the unique id of the object. (The object identity is maintained by this unique id instead of a referrence or pointer; typical in a distributed object environment).

struct some_object
{
      some_object get_object( /* ... */ int id = -1 ) 
      {
          try{ return some_object( id /* ... */ ) ; } // ok if file exists
          catch(...) { return some_object( /* ... */ ) ; }
      }

      // ...

      some_object& operator= ( const some_object& that ) 
      {
         // member-wise assign everything *except* the file_number  
         // update data in file
         return *this ;
      }
   private:
      const int file_number ;
      static int last_seq_number ; //  persistent 
      // ...
      some_object( /* ... */ ) : file_number( ++seq_number ) /* ... */ 
      { 
          // ....
          // write data to file
      }

      some_object( int id, /* ... */  ) ; // if file exists, return an object
           // initialized with data from the file; else throw something

      // ...
};

A typical example of such a (hidden) const member variable in a class is the vtable pointer in a polymorphic type. It is initialized, but never assigned to.

First, let's get the obvious out of the way. I must assume that although the data member is const, it does not have the same value for all objects of that class. Because if that were the case, the trivial solution is to use a static const data member.

So, I imagine you have something like this toy example:

class Angle {
  public:
    enum unit { radians, degrees, turns };

    double Value;
    const unit Unit;  // this is the const data member.

    Angle(double aValue, unit aUnit = radians) : Value(aValue), Unit(aUnit) { };
    Angle(const Angle& rhs) : Value(rhs.Value), Unit(rhs.Unit) { };

    Angle& operator=(const Angle& rhs) {
      Value = rhs.Value;
      Unit = rhs.Unit;   // ERROR: 'const unit' cannot appear on the left-hand-side of an assignment.
      return *this;
    };

    // ..
};

In other words, you have some object parameter that you want to set upon creation of the object, and then leave fixed from that time on-wards. At least, that's how I understand it.

My current solution is to use a pointer to a const object that lives in a more global scope. While the pointer itself is not constant, at least the underlying object is const.

It sounds pretty bad. Again, excluding the idea that the value in question is the same for all objects (in which case, the trivial solution is a static data member), I must assume that you need some sort of vector or array storage to store all the values, one for each existing instance of your class. Or, simply put, you allocate the value with new and manage it as a resource. It is not unheard of to use a pointer to get around const-ness vs. copy-assignment issues (heck! I could pinpoint some of my library code that does it, but in a different context than yours). It is never pretty, but sometimes necessary. In your case, I think it is not.

I've found threads that used casting in the assignment operator to copy const data members (seems like a bad idea)

Const-casting is generally pretty terrible idea. There are some legitimate cases for holding data members that should be excluded from constness of member functions, but that's what the mutable keyword is for. In most other cases, it is not only bad style, it can also be undefined behavior. Furthermore, I've probably programmed close to half a million lines of code in C++, and not a single instance of const_cast pops up when I search all my source code folders. I have never needed it nor was I ever tempted to use it, so can you.

and yet another that suggested avoiding the assignemt operator all together by using vector<shared_ptr<myObj>> instead of vector<myObj> for instance (seems like a pretty good idea).

This is a somewhat heavy-duty solution. Certainly safe and do-able. But I would not recommend using this solution unless this kind of use of "myObj" makes sense for "myObj". What I mean is that the general idea with shared_ptr is to share ownership of an object whose life-time is determined by its relationships to multiple objects that require it to exist. If this does not fit the description of the only possible way that the "myObj" objects fit inside your software architecture, then you shouldn't constrain that class to be only usable through a shared_ptr scheme. Generally, if you want to store the objects in a STL container, it likely means that your object doesn't fit the bill. You should not let an implementation issue (not being about to copy-assign with a const data member) dictate the semantics of how your class is used or what the user has to suffer with. Plus, it's never nice to write in your docs: "Sorry, if you want to store the objects in an STL container you need to store them with shared_ptrs, because I'm too incompetent to find a solution to some implementation issue."

Which would generally be the best solution or is it a case by case kind of thing?

Personally, I really don't understand the obsession with having a const data member. Constness is generally used to restrict the usage of an object to read-only operations. When you are talking about a data member, you can easily do that without needing const at all. You get no performance benefit from marking something const, unless it is a global variable or a static data member, so that's not an issue either.

So, how do you restrict the use of the data member to read-only operations? You make it private, and expose only a const accessor (either by delivering the member by value, or by const-reference). Simple, easy, safe, hassle-free. What else could you need. So, our toy example turns into this:

class Angle {
  public:
    enum unit { radians, degrees, turns };
  private:
    unit Unit;    // private data member.
  public:
    double Value;

    unit getUnit() const { return Unit; };    // only a read-only accessor.

    Angle(double aValue, unit aUnit = radians) : Value(aValue), Unit(aUnit) { };

    // NOTE: both copy-assignment and copy-constructor are trivial, not needed.

    // ..
};

Of course, you could still modify the data member within a member function or friend function, but generally, the person that codes the class is the same person that codes its member functions and friend functions. At worst, if there is a possibility that a stranger comes along to add/modify some member functions, then mark, clearly, with comments around the "const" data member that it should never be modified. Because it is not accessible from anywhere else, there is no possibility that some very remote piece of code (e.g., the code of a user of your library) accesses it, it can only be accessed by someone working directly on the class' implementation, and you have to suppose that person knows what he is doing and a notification (by comment) is enough to be safe.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.