C ++ constant field initialization after constructor

I want to create an immutable data structure that, 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 can I do to fix this problem,

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

but

  • This makes me save MetaData as a field in my object. Which I do not always want.
  • Sometimes the logic in the constructor is much more complicated than a single read (say, error handling can take several lines)

So the only solution I was thinking about is matching the lines

 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 are allowed to initialize final in the constructor).

+7
c ++ constructor const
source share
10 answers

You can discard the constant in the constructor:

 class Image { public: const int width,height; Image(const char *filename) : width(0), height(0) { MetaData md(readDataFromFile(filename)); int* widthModifier = const_cast<int*>(&width); int* heightModifier = const_cast<int*>(&height); cout << "Initial width " << width << "\n"; cout << "Initial height " << height << "\n"; *widthModifier = md.GetWidth(); *heightModifier = md.GetHeight(); cout << "After const to the cleaners " << width << "\n"; cout << "After const to the cleaners " << height << "\n"; } }; 

This will allow you to achieve what you want to do, but I must say that personally I will stay away from this and fear any public data users (at least in relation to your specific example). I would like to go with Georg or make the data confidential and provide only the recipient.

+2
source share

You can move width and height to the same type and move the initialization code to a helper initialization 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)) {} 
+11
source share

You can simply use NamedConstructor 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 advantages of Named Constructors is their obviousness: the name indicates that you are creating your object from a file. Of course, this is somewhat more verbose:

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

But it never bothered me.

+6
source share

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()) { } }; 
+4
source share

First, you must understand that the body of the constructor is intended only to run code to complete the initialization of your object as a whole; members must be fully initialized before entering the body.

Ergo, all members are initialized in the (implicit, if not explicit) initialization list. Obviously, const variables must be initialized in the list, because as soon as you enter the body, they are already supposed to be initialized; you are just trying to assign them.

Typically, you do not have const members. If you want these members to be unchanged, just do not give them public access that could change them. (In addition, the presence of const members makes your class unassignable, as a rule, unnecessarily.) Going along this route easily eliminates your problem, because you simply assigned them values ​​in the constructor body as you wish.

The method you want to do while maintaining const can 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 do not think this route is worth it.

+2
source share

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

 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; }; 

PS: I used to write personal things at the end, because they are less important for the class user.

+1
source share

How to pass MetaData as an argument to the constructor. This has many advantages:

a) The constructor interface makes it clear that you are dependent on MetaData. b) This makes it easier to test the Image class with various types of metadata (subclasses)

So, I would suggest that it looks like this:

 struct MD{ int f(){return 0;} }; struct A{ A(MD &r) : m(rf()){} int const m; }; int main(){} 
0
source share

I would use a static method:

 class Image { public: static Image* createFromFile( const std::string& filename ) { //read height, width... return new Image( width, height ); } //ctor etc... } 
0
source share
 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 as I understand, I think this mechanism will work.

0
source share

This is one of my least favorite aspects of C ++ compared to Java. I will use the example I worked on when I needed to solve this problem.

What follows is the equivalent of the readObject method. It deserializes the video key from the provided file path.

 #include <fstream> #include <sstream> #include <boost/archive/binary_iarchive.hpp> #include <boost/archive/binary_oarchive.hpp> using namespace std; using namespace boost::filesystem; using namespace boost::archive; class VideoKey { private: const string source; const double fps; const double keyFPS; const int numFrames; const int width; const int height; const size_t numKeyFrames; //Add a private constructor that takes in all the fields VideoKey(const string& source, const double fps, const double keyFPS, const int numFrames, const int width, const int height, const size_t numKeyFrames) //Use an initializer list here : source(source), fps(fps), keyFPS(keyFPS), numFrames(numFrames), width(width), height(height), numKeyFrames(numKeyFrames) { //Nothing inside this constructor } public: //Then create a public static initializer method that takes in //the source from which all the fields are derived //It will extract all the fields and feed them to the private constructor //It will then return the constructed object //None of your fields are exposed and they are all const. const static VideoKey create(const path& signaturePath) { const path keyPath = getKeyPath(signaturePath); ifstream inputStream; inputStream.open(keyPath.c_str(), ios::binary | ios::in); if (!inputStream.is_open()) { stringstream errorStream; errorStream << "Unable to open video key for reading: " << keyPath; throw exception(errorStream.str().c_str()); } string source; double fps; double keyFPS; int numFrames; int width; int height; size_t numKeyFrames; { binary_iarchive inputArchive(inputStream); inputArchive & source; inputArchive & fps; inputArchive & keyFPS; inputArchive & numFrames; inputArchive & width; inputArchive & height; inputArchive & numKeyFrames; } inputStream.close(); //Finally, call your private constructor and return return VideoKey(source, fps, keyFPS, numFrames, width, height, numKeyFrames); } 
0
source share

All Articles