代码之家  ›  专栏  ›  技术社区  ›  Elazar Leibovich

在构造函数之后初始化C++ const字段

  •  8
  • Elazar Leibovich  · 技术社区  · 15 年前

    我想创建一个不可变的数据结构,比如说,可以从一个文件初始化。

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

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

    然而

    1. 它迫使我将元数据保存为对象中的字段。我并不总是想要。

    所以我想到的唯一解决办法就是

    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 */{}
    };
    

    Java ,允许初始化 final 在构造函数中)。

    10 回复  |  直到 15 年前
        1
  •  2
  •   celavek    7 年前

    你可以抛弃构造器中的常量:

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

    未定义的行为 cppreference )

    const \u cast可以形成指向 通过非常量修改常量对象 访问路径。。。导致未定义的行为。

    我会害怕任何公开数据的成员(至少在关于你的具体例子)。 我会采用Georg的方法,或者将数据私有化,只提供getter。

        2
  •  12
  •   Georg Fritzsche    15 年前

    width height

    // 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)) {}
    
        3
  •  7
  •   Matthieu M.    15 年前

    您只需使用 NamedConstructor

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

    命名构造函数的一个主要优点是其明显性:该名称表示您正在从文件构建对象。当然,更详细一点:

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

    但这从未困扰过我。

        4
  •  4
  •   Sadeq    15 年前

    class Image
    {
      public:
    
        const int width, height;
    
        Image(const char* filename) : Image(readDataFromFile(filename)) { }
        Image(const MetaData& md) : width(md.width()), height(md.height()) { }
    };
    
        5
  •  3
  •   GManNickG    15 年前

    首先,您应该了解构造函数体只是为了运行代码 完成 初始化整个对象;在进入主体之前,成员必须完全初始化。

    因此, 成员在(隐式的,除非显式的)初始化列表中初始化。显然, const

    一般来说,你没有 常数 成员。如果你想让这些成员是不可变的,就不要给他们任何可能改变他们的公共访问权(还有,有 常数 会员使你的班级不可转让;通常是不必要的)走这条路线很容易解决问题,因为您只需在构造函数的主体中按您的意愿为它们赋值。

    可以是:

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

    我认为这条路不值得走。

        6
  •  2
  •   Notinlist    15 年前

    应该为width和height添加内联getter,而不是public const成员变量。编译器将使这个解决方案与最初的尝试一样快。

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

    附言:我过去常常在结尾写一些私人的东西,因为它们对类的使用者来说不太重要。

        7
  •  0
  •   Chubsdad    15 年前

    a) 构造函数接口明确了对元数据的依赖性。 b) 它有助于使用不同类型的元数据(子类)测试图像类

    所以,我可能会建议如下:

    struct MD{
       int f(){return 0;}
    };
    
    struct A{
       A(MD &r) : m(r.f()){}
       int const m;
    };
    
    int main(){}
    
        8
  •  0
  •   Frank Osterfeld    15 年前

    我会使用静态方法:

    class Image {
    public:
        static Image* createFromFile( const std::string& filename ) {
            //read height, width...
            return new Image( width, height ); 
        } 
    
        //ctor etc...
    }
    
        9
  •  0
  •   dalle    15 年前
    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;
    }
    

    据我所知,我认为这个机制会起作用。

        10
  •  0
  •   HesNotTheStig    10 年前

    这是我最不喜欢的C++方面之一,与java相比。当我需要解决这个问题时,我会用一个我正在研究的例子。

    下面的内容相当于readObject方法。它从提供的文件路径反序列化视频密钥。

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