How do I assign a data object with const members?
Asked Answered
B

4

5

Hope this is not a duplicate. If so, please point me to it in a comment and I'll remove the question again.

I have a data object with data that's only valid in a bundle - i.e. there's no sense in changing the value of one member without invalidating the other members.
This data object describes some image information:

struct ImageInfo
{
    ImageInfo(const double &ppix, ...)
        : PpiX(ppix),
        ...
    { }

    const double PpiX;
    const double PpiY;
    const int SizeX;
    const int SizeY;
};

In my image object I have a non-const member of type ImageInfo:

class MyImageObject
{
    ...
private:
    ImageInfo mMyInfo;
}

I want to be able to change mMyInfo at runtime, but only so that it will take a new ImageInfo(...) instance.

In the MyImageObject::Load() function, I'd like to read this data from the file info and then create an ImageInfo instance with the correct set of data:

double ppix = ImageFile.GetPpiX();
...
mMyInfo = ImageInfo(ppix, ...);

But I couldn't manage to write a valid assignment operator (copy constructor is possible of course). My solution left mMyInfo empty, because I didn't reference this:

ImageInfo operator=(const ImageInfo &other)
{
    // no reference to *this
    return ImageInfo(other);
}

Out of curiosity I'd like to know how the assignment operator for such a class would need to look like.

I'm using plain C++.

EDIT
Possible solutions (the goal is to keep the data transportable, but coherent):

  • Use private members together with Get...() functions -> simple, but I'd like to avoid the parentheses.
  • Store a pointer to ImageInfo: ImageInfo *mpMyInfo; (I'd like to avoid the heap.)
  • Use serialization and store the serialized ImageInfo, then create local instances from the serialized data.
Brillatsavarin answered 11/6, 2013 at 13:10 Comment(5)
You could try casting away the 'constness' of the member variables in order to make the assignment, asfaik, this is valid to do when the object itself is not const.Commissioner
Why are those fields const to begin with?Ragan
@delnan They don't need to be const, they need to be readonly. But readonly exists in C#, but not in C++.Brillatsavarin
Whether const or readonly, you obviously want to change these values over time, so why declare that they don't change?Ragan
@delnan I guess I'm thinking "object" too much. Is the following correct? The member mMyInfo is on the stack, so its members occupy stack fields marked const. In C++ it's not possible to "replace" the mMyInfo instance with another ImageInfo instance, right?Brillatsavarin
T
4

I don't think you can have const member variables that aren't static. If you need const variables that change with the instance, you could do something like this:

struct ImageInfo
{
private:
    double myPpiX;
    double myPpiY;
    int mySizeX;
    int mySizeY

public:
    ImageInfo(const double &ppix, ...)
        : myPpiX(ppix),
          PpiX(myPpiX),
        ...
    { }

    ImageInfo( const ImageInfo &other)
    : myPpiX( other.myPpiX),
      PpiX(myPpiX)
     ...
    { }

    const double &PpiX;
    const double &PpiY;
    const int &SizeX;
    const int &SizeY;

    // EDIT: explicit assignment operator was missing
    ImageInfo& operator=(const ImageInfo &other)
    {
        myPpiX  = other.myPpiX;
        myPpiY  = other.myPpiX;
        mySizeX = other.mySizeX;
        mySizeX = other.mySizeX;
        return *this;
    }
};

The values are stored in the private variables that can be set at construction, and their values accessed by const references. You're also not dependent on the references passed into the constructor living as long as the ImageInfo instance.

Tupper answered 11/6, 2013 at 13:30 Comment(6)
This doesn't permit operator=, because references cannot be reseated.Jeb
Yakk, good point. You'd have to write an = operator and a copy constructor.Tupper
@Yakk So is it possible to write an assignment operator for this?Brillatsavarin
@Martin I originally thought not, but maybe you can. The references cannot be reseated, but maybe we don't want them to be reseated!Jeb
@Yakk I implemented the idea and it actually works great. I'll edit HeywoodFloyd's answer to add the missing assignment operator.Brillatsavarin
@Tupper This is the answer I was searching for. It's a pity I can only upvote you once...Brillatsavarin
J
2

As they are data fields that can be modified, they are not const.

If you want to restrict post-construct access to them to const, you need to wrap them in accessors as follows:

struct ImageInfo
{
  ImageInfo(const double &ppix, /*...*/)
    : PpiX_(ppix),
    /*...*/
  { }
  double const& PpiX() const {return PpiX_; };
  double const& PpiY() const {return PipY_; };
  int const& SizeX() const {return SizeX_; };
  int const& SizeY() const {return SizeY_; };
private:
  double PpiX_;
  double PpiY_;
  int SizeX_;
  int SizeY_;
};

That allows move/copy assignment and construction, while blocking non-const access outside of said construction.

Avoiding the () is tricky, but could be done with pseudo-references, something like this:

struct pseudo_const_reference_to_Ppix {
  ImageInfo const* self;
  operator double() const { return self->Ppix; }
  void reseat( ImageInfo const* o ) { self = o; }
};

plus a whole pile of boilerplate to overload every const operator on the left and right such that the above pseudo_const_reference_* is just as valid as double.

Generic versions can be written (either taking a functor or a std::function if you are willing to suffer type erasure overhead).

Then you maintain these pseudo-const references on assignment and copy/move construction.

I think the () is the better option.

Note that the overhead of a pointer (or more) per pseudo-reference is basically unavoidable: member variables do not have access to the this from which they are invoked, even though the accesing site has it right there plain as day.

Jeb answered 11/6, 2013 at 13:33 Comment(1)
@Martin I have now included a sketch of a ridiculous solution that gets rid of the ().Jeb
H
1

If something is const, you can't change it. Full stop.

So you must adjust the design somewhere, either not have those ImageInfo members const, not have ImageInfo as member, or best: not do the assignment.

Normally const members are set in constructor. You can make a load function that creates a MyImageObject object with all its content, so avoiding to have a half-done thing and load the sate in a second phase.

An alternative is to have the mMyInfo indirectly, say using unique_ptr, then you can replace it with another instance. I would not do that without a really good reason.

Hessler answered 11/6, 2013 at 13:26 Comment(0)
T
0

Immutable value objects are great. But the variable holding the value object (mMyInfo in MyImageObject) should be (non-constant) a pointer. In other languages (e.g. Java) this is automatically the case, but not in C++, where you need the * operator. Also, there is no need to override/implement the = operator for value objects. To change the image data within the image object, you assign a newly constructed ImageInfo object to the myImageInfo pointer. This way, none of the internal variables of the value object are changed.

Thready answered 15/2, 2015 at 20:7 Comment(1)
If it's only a POD that replaces a bunch of member variables by a single one, I think the approach from HeywoodFloyd's answer is ideal. If there is a need to share that information, I'd have a shared pointer of that type instead (NULL-initialized). Greetings to Chile!Brillatsavarin

© 2022 - 2024 — McMap. All rights reserved.