2010-08-12 2 views
7

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

+1

Зачем вам нужны элементы изображения, которые будут 'const'? Непрерывная структура данных будет лучше выражаться экземпляром 'const' класса структуры данных, а не экземпляром класса структуры данных, все члены которого являются' const'. Если вы будете следовать этому подходу, у вас нет никаких проблем в вашем конструкторе; 'const' -ness вашего объекта начинается только после завершения конструктора. –

+0

В значительной степени это - не делайте переменные-члены const, действительно. – Puppy

+0

@Charles, я хочу, чтобы все изображения были как const, насколько это возможно. Я не хочу, чтобы размер объекта менялся любым программистом после меня в методе внутри 'Image'. Я не хочу отслеживать, кто изменил этот размер изображения, если размер изображения отличается в двух местах, я могу заключить, что это 100% утечка памяти, а не ленивый программист, создающий ярлыки. Существует много веских оснований для предпочтения силы и * связи * определенное поле не должно изменяться. –

ответ

2

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

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

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

+0

подумал об этом, но мне не очень нравится этот подход. Но это, наверное, лучший подход. Мне просто нужно его правильно документировать ('ImitateJavasFinalChangableInConstructor' ...). –

+1

@Elazar: Не пытайтесь запрограммировать Java на C++. Программа C++ в C++, забудьте, что знаете Java. – GManNickG

+2

@GMan, мне все равно, если это Java или нет. Наличие 'width' и' height' как 'const' - отличная идея:' Java', 'Scala',' Haskell', 'C' или' C++ '. В 'Java' это проще сделать, в' C++' вам нужно пройти через обручи. Я действительно не думаю, что парадигма константных полей является специфичной для Java или зависит от специальных черт Java. –

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)) {} 
+0

Это упрощенное решение, которое я уже определил. (Я не спустил вас вниз, но, возможно, downvotes - это то, что я предложил очень похожее решение, и ваше решение не укусит основную проблему). Что делать, если у меня 10 таких полей? Что делать, если у меня есть некоторый сложный код для передачи ошибок для инициализации каждой из этих переменных? Это не всегда работает. –

+0

@ Элазар: Может, я что-то пропустил, но я не вижу такого же подхода в вашем вопросе? Вы также можете добавлять дополнительные поля в вспомогательную структуру, которая может быть вложенным классом, если это необходимо. Я также не понимаю, почему обработка ошибок в помощнике инициализации не может быть более сложной? –

+0

@GeorgFritzsche приятное решение, но изображение может частным образом наследовать размер, и таким образом можно получить прямой доступ к высоте/ширине от базового класса, который их инициализировал. – Ghita

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

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

+0

Я хочу, чтобы эти поля были неизменными * изнутри объекта *. Я хочу заставить конструктора никогда не изменять ширину или высоту 'Image'. Я не могу заставить это с помощью геттеров и сеттеров. Я знаю **, что это то, как работает C++ (ctor выполняется после членов), но java также работает таким образом и имеет обходные пути для 'final'. –

+0

@ Elazar: Ну, это не Java, поэтому нет смысла упоминать об этом. Я думаю, что ваш лучший выбор состоит в том, чтобы превратить ваш неизменный в базовый класс, и ваше «изображение» может наследовать его. Еще лучше, так как это база в любом случае, просто избавиться от const и снова, предоставить const-accessors. Поскольку это базовый класс и * вы * контролируете это, вы знаете, что ничто другое не может манипулировать этими значениями в любом случае. – GManNickG

+0

Это хорошая идея, если вы работаете в небольшой команде. В большой компании и кодовой базе, которая пройдет много рук, я думаю, что я должен предположить, что следующий, кто изменяет этот класс, может быть не мной (или, может быть, я уже забыл, что он везде предполагал, что класс Image неизменен). –

1

Вы должны добавить встроенные getters для ширины и высоты вместо переменных public const member. Компилятор сделает это решение так же быстро, как оригинальная попытка.

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

+0

Я хочу, чтобы будущий разработчик 'Image' не менял' width' и 'height' внутри. См. Мой комментарий для GMan. Ваша точка зрения в P.S. хороший! Благодаря! –

+0

@ Элазар Лейбович: Производные классы не будут видеть ширину и высоту. Методы в не-производных классах могут вызвать проблемы. Mathieu M. rock :-) с его статическим методом фабрики. – Notinlist

+1

Я не говорю о клиенте, который наследует от классов в моем коде. Я говорю о другом разработчике, который изменит мой код и исправит ошибки в моем коде. В конце концов, он мой товарищ по команде ... Я хочу сообщить этому парню ясно: «мы никогда не касаемся этих полей во время жизни объекта». И я буду рад, если компилятор помешает ему это сделать. Добавление полей 'const' заставит его объяснить рецензенту, почему это нормально, чтобы удалить их' const'ness. Приведение их в личную жизнь не скажет ему, что он не может исправить мой класс, чтобы изменить их. –

0

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

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

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

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

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

int main(){} 
+0

Это делает инициализацию 'A' труднее, и иногда существует множество сложных типов и логики MD, которые мне нужно инициализировать. –

0

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

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

    //ctor etc... 
} 
+2

Не используйте 'new', пожалуйста ... –

+1

@Matthieu M: Не использовать новые когда-либо? Не использовать новые функции статического члена? Полезный комментарий? Надеюсь, вы не прокомментируете свой код, как вы это сделали. – Sam

+0

@Sam, @Frank, кто удалит новое изображение, которое вы только что создали? Откуда вы знаете, что деструктор изображения не удаляет его потом? Этот подход очень проблематичен. –

6

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

Одним из главных преимуществ Named Constructors является их очевидность: имя указывает, что вы создаете свой объект из файла. Конечно, это несколько более подробно:

Image i = Image::FromFile("foo.png"); 

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

+1

@ Matthie, это решает в некоторой степени. Однако вы использовали неявный конструктор копирования изображения, который не так велик. (Я думаю, вам просто нужно C++ ...) –

+0

@ Элазар: Почему это не так здорово? – GManNickG

+0

На самом деле, несмотря на то, что копия необходима, маловероятно, что конструктор копирования будет выполнен здесь: Оптимизация возвращаемого значения заботится о производительности. Обратите внимание, что в C++ 0x мы будем использовать семантику 'move' здесь, хотя также необязательно ссылаться на конструктор перемещения. –

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

Очень хорошее предложение – Ghita

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

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

+1

Erm ... 'height' и' width' не являются 'const', поэтому я действительно не вижу вашей точки здесь ... –

+0

Если вы считаете Test Class, то вы можете увидеть там сами высоты и wieght const. Поэтому, если вы считаете, что класс Image - это не что иное, как тестовый класс, в этом случае вы можете узнать, что переменные-члены являются const.На самом деле моя точка зрения заключается в том, чтобы показать, не вставляя класс obj, то есть класс A, вы можете назначить константные переменные для класса Test из класса A, и в этом случае фактически конструктор копирования также будет действовать как конструктор по умолчанию. Поэтому я думаю, что моя цель будет решить. – indrajit

0

Это один из моих наименее любимых аспектов 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); 
    } 
Смежные вопросы