Я хочу создать неизменную структуру данных, которая, скажем, может быть инициализирована из файла.
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
s в конструкторе).
Вы можете убрать константность в конструкторе:
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-объекта через неконст путь доступа ... приводит к неопределенному поведению.
Я бы опасался любых открытых членов данных (по крайней мере, в отношении вашего конкретного примера). Я бы выбрал подход Георга или сделал данные приватными и предоставил только геттер.
Как насчет передачи метаданных в качестве аргумента конструктору. Это дает много преимуществ:
a) Интерфейс конструктора проясняет зависимость от MetaData. б) Это облегчает тестирование класса Image с различными типами метаданных (подклассами)
Итак, я бы, вероятно, предложил следующее:
struct MD{
int f(){return 0;}
};
struct A{
A(MD &r) : m(r.f()){}
int const m;
};
int main(){}
Вы можете просто использовать идиому 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");
Но меня это никогда не беспокоило.
Если бы это был 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()) { }
};
Во-первых, вы должны понимать, что тело конструктора предназначено только для выполнения кода для завершения инициализации вашего объекта в целом; перед вводом тела члены должны быть полностью инициализированы.
Итак, все члены инициализируются в списке инициализации (неявном, если не явным). Ясно, что переменные 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)))
{}
};
Я не думаю, что этот маршрут того стоит.
Вы должны добавить встроенные геттеры для ширины и высоты вместо общедоступных константных переменных-членов. Компилятор сделает это решение так же быстро, как и первоначальная попытка.
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 .: Раньше я писал личные вещи в конце, потому что они менее важны для пользователя класса.
Я бы использовал статический метод:
class Image {
public:
static Image* createFromFile( const std::string& filename ) {
//read height, width...
return new Image( width, height );
}
//ctor etc...
}
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;
}
Насколько я понимаю, я думаю, что этот механизм будет работать.
Вы можете переместить 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)) {}