Инициализация полей константы C++ после конструктора

Я хочу создать неизменную структуру данных, которая, скажем, может быть инициализирована из файла.

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. Иногда логика в конструкторе намного более сложна, чем единственное чтение (скажите, обработка ошибок может проводить несколько строк),

Таким образом, единственное решение, о котором я думал, вроде

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, Вам разрешают, инициализируя finals в конструкторе).

8
задан Elazar Leibovich 12 August 2010 в 06:43
поделиться

9 ответов

Вы можете убрать константность в конструкторе:

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-объекта через неконст путь доступа ... приводит к неопределенному поведению.

Я бы опасался любых открытых членов данных (по крайней мере, в отношении вашего конкретного примера). Я бы выбрал подход Георга или сделал данные приватными и предоставил только геттер.

2
ответ дан 5 December 2019 в 05:54
поделиться

Как насчет передачи метаданных в качестве аргумента конструктору. Это дает много преимуществ:

a) Интерфейс конструктора проясняет зависимость от MetaData. б) Это облегчает тестирование класса Image с различными типами метаданных (подклассами)

Итак, я бы, вероятно, предложил следующее:

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

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

int main(){}
0
ответ дан 5 December 2019 в 05:54
поделиться

Вы можете просто использовать идиому 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");

Но меня это никогда не беспокоило.

7
ответ дан 5 December 2019 в 05:54
поделиться

Если бы это был 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()) { }
};
4
ответ дан 5 December 2019 в 05:54
поделиться

Во-первых, вы должны понимать, что тело конструктора предназначено только для выполнения кода для завершения инициализации вашего объекта в целом; перед вводом тела члены должны быть полностью инициализированы.

Итак, все члены инициализируются в списке инициализации (неявном, если не явным). Ясно, что переменные 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)))
    {}
};

Я не думаю, что этот маршрут того стоит.

2
ответ дан 5 December 2019 в 05:54
поделиться

Вы должны добавить встроенные геттеры для ширины и высоты вместо общедоступных константных переменных-членов. Компилятор сделает это решение так же быстро, как и первоначальная попытка.

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 .: Раньше я писал личные вещи в конце, потому что они менее важны для пользователя класса.

2
ответ дан 5 December 2019 в 05:54
поделиться

Я бы использовал статический метод:

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

    //ctor etc...
}
0
ответ дан 5 December 2019 в 05:54
поделиться
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;
}

Насколько я понимаю, я думаю, что этот механизм будет работать.

0
ответ дан 5 December 2019 в 05:54
поделиться

Вы можете переместить 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)) {}
11
ответ дан 5 December 2019 в 05:54
поделиться
Другие вопросы по тегам:

Похожие вопросы: