views:

230

answers:

9

I want to create an immutable data structure which, say, can be initialized from a file.

class Image {
public:
   const int width,height;
   Image(const char *filename) {
     MetaData md((readDataFromFile(filename)));
     width = md.width();   // Error! width  is const
     height = md.height(); // Error! height is const
   }
};

What I could do to fix the problem is

class Image {
   MetaData md;
public:
   const int width,height;
   Image(const char *filename):
     md(readDataFromFile(filename)),
     width(md.width()),height(md.height()) {}
};

However

  1. It forces me to save MetaData as a field in my object. Which I don't always want.
  2. Sometimes the logic in the constructor is much more complex than a single read (say, error handling can take a few lines)

So the only solution I thought of is along the lines of

class A {
  int stub;
  int init(){/* constructor logic goes here */}
  A():stub(init)/*now initialize all the const fields you wish
  after the constructor ran */{}
};

Is there a better idea? (In Java, you're allowed initializing finals in the constructor).

+5  A: 

You could move width and height into one type and move the initialization code into an initialization helper function:

// header:
struct Size { 
    int width, height;
    Size(int w, int h) : width(w), height(h) {}
};

class Image {
    const Size size; // public data members are usually discouraged
public:
    Image(const char *filename);
};

// implementation:
namespace {
    Size init_helper(const char* filename) {
        MetaData md((readDataFromFile(filename)));
        return Size(md.width(), md.height());
    }
}

Image::Image(const char* filename) : size(init_helper(filename)) {}
Georg Fritzsche
I'm all for downvotes if there is something wrong with an answer - but then *i'd like to know what* so i can fix or delete.
Georg Fritzsche
Even more strange because our solutions are the same concept, and I have no -1. +0.5 for sympathy, +0.5 for beating me to the solution.
GMan
@Georg, this is a simplistic solution I already purposed. (I didn't downvote you, but maybe the downvotes are because I proposed a very similar solution, and that your solution doesn't bite the main problem).What if I have 10 such fields? What if I have some complex error handing code to initialize each of those variables? This doesn't always work.
Elazar Leibovich
@Elazar: Maybe i missed something, but i don't see that same approach in your question? You could also always add more fields to a helper struct which could be a nested class if necessary. I also don't see why the error handling in the initialization helper couldn't be more complex?
Georg Fritzsche
+1  A: 

First, you should understand the constructor body is just for running code to complete initializing your object as a whole; the members must be completely initialized before the body is entered.

Ergo, all members are initialized in an (implicit unless made explicit) initialization list. Clearly, const variables must be initialized in the list because once you enter the body, they are already suppose to be initialized; you'd simply be trying to assign them.

Generally, you don't have const members. If you want those members to be immutable, just don't give any public access to them that could change them. (Also, having const members make your class non-assignable; typically unnecessarily.) Going this route easily fixes your problem, as you'd just assign them values in the body of the constructor like you wish.

A method to do what you want while maintaining const could be:

class ImageBase
{
public:
    const int width, height;

protected:
    ImageBase(const MetaData& md) :
    width(md.width()),
    height(md.height())
    {}

    // not meant to be public to users of Image
    ~ImageBase(void) {} 
};

class Image : public ImageBase
{
public:
    Image(const char* filename) : // v temporary!
    ImageBase(MetaData(readDataFromFile(filename)))
    {}
};

I don't think this route is worth it.

GMan
@GMan, I want this fields to be immutable *from within the object*. I want to force the implementor never to change the `Image`'s width or height. I can't force that with getter and setters alone. I **know** that that's how C++ works (ctor executes after members), but java also works that way, and have workarounds for `final`s.
Elazar Leibovich
@Elazar: Well, this isn't Java so there's no point in mentioning it. I think your best bet is to factor your immutable's out into a base class, and your `Image` can inherit from it. Even better, since it's a base anyway, just get rid of the const and again, provide const-accessors. Since it's the base class and *you* control that, you know nothing else can manipulate those values anyway.
GMan
@GMan, it's a good idea if you work in a small team. In a big company and a codebase that would pass many hands, I think I must assume that the next one who changes this class might not be me (or might be me which already forgot he assumed everywhere that the Image class is immutable).
Elazar Leibovich
+1  A: 

You should add inline getters for the width and height instead of public const member variables. The compiler will make this solution as fast as the original try.

class Image {
public:
   Image(const char *filename){ // No change here
     MetaData md((readDataFromFile(filename)));
     width = md.width();
     height = md.height();
   }
   int GetWidth() const { return width; }
   int GetHeight() const { return height; }
private:
   int width,height;
};

P.S.: I used to write private things at the end because they are less important for the user of the class.

Notinlist
@Notinlist, I want the future implementor of `Image` not to change `width` and `height` internally. See my comment for GMan. Your point in the P.S. is good! Thanks!
Elazar Leibovich
@Elazar Leibovich: Derived classes will not see width and height. Methods in not-derived classes may cause troubles. Mathieu M. rocks :-) with his static factory method.
Notinlist
@Notinlist, I'm not talking about client that will inherit from classes in my code. I'm talking about fellow developer which will change my code and fix bugs in my code. He's my teammate after all... I want to communicate to this guy clearly "we never touch this fields during the object's life". And I'll be glad if the compiler will prevent him from doing so. Adding `const` fields will force him to explain to the reviewer why is it OK to remove their `const`ness. Making them private won't tell him he can't patch my class to change them.
Elazar Leibovich
A: 

How about passing MetaData as an argument to the constructor. This gives a lot of benefits:

a) The constructor interface makes it clear about the dependency on MetaData. b) It facilitates testing of the Image class with different types of MetaData (subclasses)

So, I would probably suggest similar to follows:

struct MD{
   int f(){return 0;}
};

struct A{
   A(MD &r) : m(r.f()){}
   int const m;
};

int main(){}
Chubsdad
It makes initializing `A` harder, and sometimes there are many complex `MD` types and logic I need to initialize.
Elazar Leibovich
A: 

I'd use a static method:

class Image {
public:
    static Image* createFromFile( const std::string& filename ) {
        //read height, width...
        return new Image( width, height ); 
    } 

    //ctor etc...
}
Frank
Don't use `new` please...
Matthieu M.
@Matthieu M: Don't use new ever? Don't use new in static member functions? Useful comment? I hope you don't comment your code as you have done here.
Sam
@Sam, @Frank, who will delete the new Image you just created? How do you know that the destructor of Image doesn't delete it afterwards? This approach is very problematic.
Elazar Leibovich
@Sam: "Don't use `new` [unnecessarily] please..." (It's only necessarily when the lifetime of an object needs to be independent from a scope. Of course, you want movable or shared semantics on an automatically allocated object to avoid leaks.)
GMan
Okay, long version: returning by copy will be much more efficient (RVO) and will not cause any issue of object lifetime control. The short version was destined to a Java/C# programming style, in C++ it's not necessary to use `new` each time you wish to create an object, and it's often harmful. Note that I didn't use the `ever`, I merely suggested not to use `new` here. As for my code ? I don't comment it ;)
Matthieu M.
Yeah, I agree that in this case, returning a copy would make a lot more sense. I guess I fiddled too much with polymorphic types etc. ;)
Frank
@Elazar Leibovich:Although I agree that using new here is not needed, as Image can be copied and assigned, I don't get your objection either. The caller of the create function is the owner of course, and what has the dtor to do with it? (A dtor is called on deletion, not vice-versa)
Frank
@Frank, remember your client see only the signature of `createFromFile`. He sees `Image * createFromFile(...)`. He'll get a pointer. But do he need to delete the pointer when he don't need that? For the function `FILE *fopen(...)` the answer is NEVER. For you function the answer is *always delete that*. How can your client know if your image is like `C`'s `FILE* fopen` or like `void* malloc`?
Elazar Leibovich
@Frank, about what I said "the destructor of Image", that's wrong. I meant that, for instance, your class can have a static `destroyAllImages` static function that kills all allocated images at once. And that's how you're supposed to get rid of the images. Deletion is not the only solution. Read "Effective C++", I'll try look for the exact item next week and refer you to it exactly.
Elazar Leibovich
@Elazar Leibovich: Well, that's what (API) documentation is for :)Personally, if I call a _static_ factory function a la Foo* createFoo() and unless documented otherwise, I assume that the caller becomes the owner. Bookkeeper singletons or static cleanup functions are the exception in "my world" (and bad design to me unless there is a very good reason to use them (which sure exist, don't have Effective C++ at hand to see the examples)).
Frank
+3  A: 

You can simply use the NamedConstructor idiom here:

class Image
{
public:
  static Image FromFile(char const* fileName)
  {
    MetaData md(filename);
    return Image(md.height(), md.width());
  }

private:
  Image(int h, int w): mHeight(h), mWidth(w) {}

  int const mHeight, mWidth;
};

One of the main advantage of Named Constructors is their obviousness: the name indicates you are building your object from a file. Of course it's slightly more verbose:

Image i = Image::FromFile("foo.png");

But that never troubled me.

Matthieu M.
@Matthie, that's solves is to some extent. However you used implicitly the copy constructor of Image, which is not so great. (I guess you just gotta love C++...)
Elazar Leibovich
@Elazar: Why is it not so great?
GMan
Actually, even though it's necessary for the copy to be possible, it's unlikely the copy constructor will be executed here: Return Value Optimization takes care of performance. Note that in C++0x, we would use `move` semantics here, though it's also unnecessary to invoke the move constructor.
Matthieu M.
@GMan, well, if I want to keep the image DATA on the stack as a field in image? It sometimes makes sense in real time embedded systems. Maybe I'm using resources which is not OK to have two identical classes pointing to them, in which case I want the copy constructor of this class private.
Elazar Leibovich
@Elazar: in C++0x you would properly define the move constructor and make the copy constructor private and it would work. Without move semantics it's messy (unfortunately). I still don't get your example though, obviously the DATA would be a private field of image.
Matthieu M.
@Matthieu, I ment that if my class will be very big, then without RVO calling the copy constructor might be expensive. I'm not familiar with C++0x, But I definitely see your point here.
Elazar Leibovich
@Elazar: yes without RVO you would have an unnecessary copy, however here we have Unnamed RVO (ie RVO applied to a nameless variable, also called rvalue by standardista). `gcc` will use URVO even in -O0, it is such a common optimization :)
Matthieu M.
+1  A: 
celavek
@Marius, thought about it, but I don't really like this approach. But that's probably the best approach so far. I just need to document it properly (`ImitateJavasFinalChangableInConstructor`...).
Elazar Leibovich
@Elazar: Stop trying to program Java in C++. Program C++ in C++, forget you know Java.
GMan
@GMan, I don't care if it's Java or not. Having `width` and `height` as `const` is a great idea, `Java`, `Scala`, `Haskell`, `C` or `C++`. In `Java` it's easier to do, in `C++` you have to go through hoops. I really don't think the paradigm of const fields is Java specific or depends on `Java`'s special traits.
Elazar Leibovich
@GMan, and BTW my question was to find out what's the idiomatic `C++` way to have a const fields in your class which depends on computation. And the answer is probably - there's no such way.
Elazar Leibovich
@Elazar: Yes, there is - *a)* use functions to initialize them or *b)* wrap them in a custom class, do the computation in its ctor and have a const member of that class.
Georg Fritzsche
+1  A: 

If it was C++0x, I would recommend this (delegating constructors):

class Image
{
  public:

    const int width, height;

    Image(const char* filename) : Image(readDataFromFile(filename)) { }
    Image(const MetaData& md) : width(md.width()), height(md.height()) { }
};
PC2st
A: 
class A
{
public:
    int weight,height;

public:
    A():weight(0),height(0)
    {
    }

    A(const int& weight1,const int& height1):weight(weight1),height(height1)
    {
        cout<<"Inside"<<"\n";
    }
};

static A obj_1;

class Test
{
    const int height,weight;

public:
    Test(A& obj = obj_1):height(obj.height),weight(obj.weight)
    {
    }

    int getWeight()
    {
        return weight;
    }

    int getHeight()
    {
        return height;
    }
};

int main()
{
    Test obj;

    cout<<obj.getWeight()<<"\n";

    cout<<obj.getHeight()<<"\n";

    A obj1(1,2);

    Test obj2(obj1);

    cout<<obj2.getWeight()<<"\n";

    cout<<obj2.getHeight()<<"\n";

    return 0;
}

As far my understanding i think this mechanism will work.

indrajit
Erm... `height` and `width` are not `const`, so I really don't see your point here...
Elazar Leibovich
If you consider the Test Class then you can see there itself the height and wieght is const . So if you consider the class Image is nothing but Test class in that case you can find out that the member variables are const over there. Actually my point of writing this is to show without embedding the class obj i.e. class A you can assign const variables for Test class from class A and in this case actually the copy constructor will also act as an default constructor.So i think my purpose will be solved.
indrajit