views:

197

answers:

7

First, my latest coding is Java, and I do not want to "write Java in C++".

Here's the deal, I have to create an immutable class. It's fairly simple. The only issue is that getting the initial values is some work. So I cannot simply call initializes to initialize my members.

So what's the best way of creating such a class? And how can I expose my immutable / final properties to the outside world in C++ standards?

here's a sample class:

class Msg {
    private:
        int _rec_num;
        int _seq;
        string text;
    public:
        Msg(const char* buffer) {
            // parse the buffer and get our member here...
            // ... lots of code
        }

        // does this look like proper C++?
        int get_rec_num() { return _rec_num; }
    };
A: 

On Finalizers

There is none, you have to emulate it. Either by using a cleanup function or by having all your resources encapsulted in RAII classes. The compiler will place static machinery in your application to call destructors on your RAII classes --i.e., when they go out of scope the resources get released through the destructor.

On Immutability and Initialization

Generally if something is immutable and const-correct the class will have all of its members as const and the only time you get to "write" to them is when the class is initialized. However in your case that might not be possible.

I suggest you gather all your resources and initialize the class (via a non-default constructor with const members) once you have them. The other alternative (which I do not abide) is to have a mutator function that "claims" to be const correct but writes to the const values for a one-time post construction initialization.

Hassan Syed
Am I wrong or is a Finalizer just a crippled destructor that is called eventually? (Assuming you use good memory management by using object by preference or smart pointers as needed).
Martin York
@martin I think that makes sense -- syntactic sugar; I just had this epiphany myself as I was typing out the answer. However, it is more programmer-friendly than coming to grips with RAII. When I was a younger programmer I longed for a finalizer in C++.
Hassan Syed
@Martin: in Java you mean? If so, then a finalizer is not a crippled destructor that is called eventually. It's a pseudo-destructor that *may or may not* be called eventually. And it's not just plain "crippled" - actually it can do things which a C++ destructor cannot (validly) do, the most important and annoying of which being that it can resurrect the object being finalized, or other finalized objects to which this object refers.
Steve Jessop
In Java text they refer to the finalizer as the equivalent of the destructor (though they also encourage its non usage). The crippled part is exactly the two things you mention: 1) Resurrection: this is an undesirable side affect of the design which results in objects that are alive (not GC yet as references exist) but that has been finalized and thus will not be finalized again (a sort of zombie half dead half alive state). 2) The non deterministic manor in which the finalizer is called (or even if it is called).
Martin York
@Martin: Possibly this is intended to be included in (1), but there's also (3): even if you don't actually resurrect any objects, and the "dead" object that has already been finalized isn't reachable from any GC root, it may be reachable from the object you're *currently* finalizing. So even if you can avoid resurrection, finalized objects *still* might need to "work". See http://stackoverflow.com/questions/158174/why-would-you-ever-implement-finalize/158370#158370 for my full rant, and especially the disclaimer at the end.
Steve Jessop
+1  A: 

You're on the right track -- use getters for everything, and without any setters, your class is effectively immutable.

Don't forget some of the corner cases though -- you might want to declare the operator=() method as private and not implement it so someone can't override the object with the default compiler generated assignment operator, etc.

Andrew
+1  A: 
    // does this look like proper C++?
    int get_rec_num() { return _rec_num; }

You should use

    int get_rec_num() const { return _rec_num; }

(see the const which allows to call the member on const objects).

AProgrammer
actually it should be "const int get_rec_num() const ..."
Hassan Syed
What is the difference if he returns by value? His class will be immutable as long as there is no return by reference.
pmr
Its good convention, and if we highlight it now the OP learns another concept.
Hassan Syed
@Hassan: I'm with pmr on this. That `const` gains nothing. It's useless and meaningless.
sbi
its not meaningless if its replaced (in the future) with a int encapsulation class -- such as `struct Integer { byte[16] _data};` -- should programmers not write consistent code ?. Being lazy in C++ is not side-effect free.
Hassan Syed
If you change the return type in future, then you can change it from `int` to `const Integer`. Labelling a practice "lazy" doesn't make it a bad practice, you have to provide a reason why the alternative is beneficial. Laziness is after all one of the three virtues of a programmer (Larry Wall).
Steve Jessop
+2  A: 

I'd mark your immutable member as 'const', and assign it a value in your constructor initializer list.

I'd also parse your buffer outside of the class, and pass in the string to the constructor.

Something like this:

class Msg {
  private:
    int _rec_num;
    int _seq;
    const std::string text;
  public:
    Msg(const std::string& str) :
      text(str)
    {

    }

    // does this look like proper C++?
    int get_rec_num() const { return _rec_num; }
};

// parse the buffer and get our parameter somewhere else

NB:

You should make any member functions that do not change the state of your class internals as 'const'; as this will allow you to call them with const objects.

You should avoid inluding a using std::string in header files; as anyone who includes your header has this 'using' forced upon them.

Steven
Personally I don't mind constructors doing significant work. In this case, where the class is immutable and so all member functions are going to be `const` anyway, I think the benefit of marking the member variables `const` too is pretty negligible, and massively outweighed by the disadvantage that it's not possible to have the constructor do anything useful. I guess it depends how tiny you like your classes, though - is it good design for one class to be responsible for two things (storing a value, and also obtaining that value from a buffer)?
Steve Jessop
+4  A: 

C++ offers some nice mechanisms to make your class immutable. What you must do is:

  • declare all your public (and maybe protected) methods const
  • declare (but not define) operator= as private

This will ensure that your objects cannot be modified after they have been created. Now, you can provide access to your now immutable data members anyway you want, using const methods. Your example looks right, provided that you make it const:

int get_rec_num() const { return _rec_num; }
Gorpik
actually it should be "const int get_rec_num() const ..." – Hassan Syed
Hassan Syed
It doesn't make a difference, because for built-in types, there is no such thing as a constant rvalue. You can never write `get_rec_num() = 7`, no matter if the return type is `int` or `const int`.
FredOverflow
A: 

To make a variable immutable you have to use the const key word eg const int _rec_num. Const variables can only be initialised through an initialisation list, which gets called before any code in the constructor. This means that you cannot do any processing in the constructor which sets the const member variables.

You have two ways round this, first you can create another internal class which takes in a buffer and parses it into your variables. Put a const version of this into your MSG class and put this in the initialisation list:

class MsgInner
{
    public:
    int _rec_num;

    Msg(const char* buffer) {
        // Your parsing code
    }
};

class Msg
{
    public:
    const MsgInner msg;

    Msg(const char* buffer) : msg(buffer)
    { // any other code }
};

This is perhaps not so 'standard', but it's another perspective. Otherwise you can also do it as the other answers have suggested with get methods.

Rodion Ingles
A: 

First of all, it is possible to initialize the members efficiently and at construction time even if they were declared as const (which is neither necessary nor recommended).

I would still suggest that you split this class into two separate classes (pseudo-code):

// Msg: pure data object; copy constructible but not modifiable.
class Msg
{
public:
  Msg(int rec_num, ...)
    : rec_num_(rec_num)
    ...
  {}

  int rec_num() const
  { return rec_num_; }
  ...
private:
  // prevent copying
  Msg& operator=(Msg const&);
private:
  int rec_num_;
}; 

// MsgParser: responsible for parsing the buffer and
// manufacturing Msg's.
class MsgParser
{
public:
  static Msg Parse(char const* buffer)
  {
     ... parse ...
     return Msg(...);
  }
};

// Usage
Msg const msg = MsgParser::Parse(buffer);

This also nicely separates the concerns of holding and parsing the data into separate classes.

Cwan