Инициализация константных полей C ++ после конструктора - PullRequest
8 голосов
/ 12 августа 2010

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

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. Это заставляет меня сохранять метаданные как поле в моем объекте. Который я не всегда хочу.
  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 вы можете инициализировать final s в конструкторе).

Ответы [ 10 ]

11 голосов
/ 12 августа 2010

Вы можете переместить 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)) {}
7 голосов
/ 12 августа 2010

Вы можете просто использовать идиому 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 голосов
/ 12 августа 2010

Если бы это был 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()) { }
};
2 голосов
/ 12 августа 2010

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

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 голосов
/ 12 августа 2010

Вы должны добавить встроенные геттеры для ширины и высоты вместо открытых переменных-членов 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;
};

П.С .: Я писал в конце личные вещи, потому что они менее важны для пользователя класса.

2 голосов
/ 12 августа 2010

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

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

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

0 голосов
/ 23 февраля 2016

Это один из моих наименее любимых аспектов 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);
    }
0 голосов
/ 12 августа 2010
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 голосов
/ 12 августа 2010

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

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

    //ctor etc...
}
0 голосов
/ 12 августа 2010

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

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

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

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

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

int main(){}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...