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

时间:2010-08-12 06:43:12

标签: c++ constructor const

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

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. 它强制我将MetaData保存为对象中的字段。我并不总是想要。
  2. 有时构造函数中的逻辑比单个读取要复杂得多(例如,错误处理可能需要几行)
  3. 所以我想到的唯一解决方案就是

    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 个答案:

答案 0 :(得分:11)

您可以将widthheight移动到一种类型中,并将初始化代码移动到初始化辅助函数中:

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

答案 1 :(得分:7)

您可以在此处使用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");

但这从来没有困扰过我。

答案 2 :(得分:4)

如果它是C ++ 0x,我建议这个(委托构造函数):

class Image
{
  public:

    const int width, height;

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

答案 3 :(得分:2)

首先,您应该了解构造函数体仅用于运行代码以完成整体初始化您的对象;在进入正文之前,必须完全初始化成员。

Ergo,所有成员都在(隐式除非明确的)初始化列表中初始化。显然,const变量必须在列表中初始化,因为一旦你进入正文,它们就已经被假定为初始化了;你只是试图分配它们。

通常,您没有const个成员。如果您希望这些成员是不可变的,那么就不要让任何可以更改它们的公共访问权限。 (此外,让const成员使您的类不可分配;通常是不必要的。)使用此路由可以轻松解决您的问题,因为您只需在构造函数的主体中按照您的意愿分配它们。

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

我认为这条路线不值得。

答案 4 :(得分:2)

您应该为宽度和高度添加内联getter,而不是公共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;
};

P.S。:我过去常常写私人事物,因为它们对班级用户来说不那么重要。

答案 5 :(得分:2)

你可以在构造函数中抛弃constness:

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_cast使得可以形成一个引用或指针   非const类型实际上是指一个const对象...   通过非const修改const对象   访问路径...导致未定义的行为。

我担心任何公共数据成员(至少在你的具体例子中)。 我会选择Georg的方法,或者将数据设为私有,只提供吸气剂。

答案 6 :(得分:0)

如何将MetaData作为参数传递给构造函数。这带来了很多好处:

a)构造函数接口清楚地表明了对MetaData的依赖性。 b)它有助于使用不同类型的MetaData(子类)

测试Image类

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

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

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

int main(){}

答案 7 :(得分:0)

我使用静态方法:

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

    //ctor etc...
}

答案 8 :(得分:0)

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

据我所知,我认为这种机制可行。

答案 9 :(得分:0)

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

以下是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);
    }