我想创建一个不可变的数据结构,例如,可以从文件初始化。
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()) {}
};
然而
所以我想到的唯一解决方案就是
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
。
答案 0 :(得分:11)
您可以将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)) {}
答案 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);
}