So I'm working on a school project right now in C++, although I'm not too familiar with the language yet.
The whole project is divided in several Milestones.
1: Reading a list with different Types of Creatures and storing them in a vector
2: Reading a TGA-File and storing it in a class.
...
5: Reading a TGA-Picture for every read Creature-Type and storing it for further use. (Printing on GUI, Remove/add)
So I thought it is a good idea to store the picture for each type in the class itself, as it should only be loaded once.
The load() function in my TGAPicture class will return std::unique_ptr so I added the type as an argument in my CreatureType class.
After doing that, I got several error like this:
Error C2280 'biosim::CreatureType::CreatureType(const biosim::CreatureType &)': attempting to reference a deleted function bio-sim-qt E:\Development\C++\bio-sim-qt\bio-sim-qt\qtmain.cpp 58 1
Error (active) function "biosim::CreatureType::CreatureType(const biosim::CreatureType &)" (declared implicitly) cannot be referenced -- it is a deleted function bio-sim-qt e:\Development\C++\bio-sim-qt\bio-sim-qt\Model.cpp 15 26
So I read about 10 questions with similar titles like mine and every one pointed out, that you cant copy unique_ptr and suggested solutions like using std::move() or returning a reference.
Although I tried to use these to fix my problem, I wasn't able to do it in the slightest, probably because I'm pretty new to C++ and have never worked with unique pointers.
This is the code, that seems relevant to me:
/**
* #class CreatureType
* Object of the various CreatureTypes ingame
*/
class CreatureType {
private:
std::string name;
int strengh;
int speed;
int lifespan;
std::vector<std::string> attributes;
std::string path;
std::unique_ptr<TGAPicture> picture; //What I tried to add in order to stre my pictures
public:
CreatureType(const std::string& name
, int strengh, int speed, int lifespan
, const std::vector<std::string>& basic_strings
, const std::string& path);
/**
* Initializes list with CreatureTypes by reading from a .txt-File
*/
static CreatureList load(const std::string& file);
/**
* Printing Data in various ways
*/
void getInfo() const;
void getInfoInOneLine() const;
std::string getName() const;
int getStrengh() const;
int getSpeed() const;
int getLifespan() const;
std::vector<std::string> getAttributes() const;
std::string getPath() const;
};
}
CreatureType::CreatureType(const std::string& name
, int strengh, int speed, int lifespan
, const std::vector<std::string>& basic_strings
, const std::string& path)
: name(name),
strengh(strengh),
speed(speed),
lifespan(lifespan),
attributes(basic_strings),
path(path),
picture(TGAPicture::loadPicture(Reference::PicturePath::creatureBasePath + path)){ }
/**
* Implementation Notes:
* - Does a line not fullfill the requirenments, it will be ignored
* - #see Formation
* - Prints data with std::cout
*/
CreatureList CreatureType::load(const std::string& file) {
CreatureList creatureList;
std::ifstream fileStream; //Datei-Handle
int lineNumber = 0;
int correctLinesRead = 0;
fileStream.open(file, std::ios::in);
if (!fileStream.is_open()) {
throw FileNotFoundException(file);
}
logger << INFO << "Einlesevorgang wird gestartet\n";
//One line per loop
while (!fileStream.eof()) {
bool skipLine = false;
std::string line;
getline(fileStream, line);
lineNumber++;
... //Checking if data is valid
//Every Parameter does exist and is valid
creatureList.push_back(CreatureType(creatureArgs[0]
, strengh, speed, lifespan
, attributes, creatureArgs[5]));
correctLinesRead++;
}
return creatureList;
}
TGAPicture:
//no padding bytes
#pragma pack( push, 1 )
/**
* #struct TGAHeader
* Represents the standard TGA-Header.
*/
struct TGAHeader {
char idLength;
char colourmapType;
char imagetype;
short colourmapStart;
short colourmapLength;
char colourmapBits;
short xOrigin;
short yOrigin;
short width;
short height;
char bits;
char descriptor;
};
#pragma pack( pop )
/**
* #struct RGBA
* Represents a Pixel with a red, green, blue and possibly alpha value
*/
struct RGBA {
std::uint8_t B, G, R, A;
};
/**
* #class TGAPicture
* Class used to represent TGA-Files, that are used in the program
*/
class TGAPicture {
public:
TGAPicture(const TGAPicture& other)
: pixel(other.pixel),
header(other.header),
width(other.width),
height(other.height),
size(other.size),
bitsPerPixel(other.bitsPerPixel) {}
TGAPicture(TGAPicture&& other) noexcept
: pixel(std::move(other.pixel)),
header(std::move(other.header)),
width(other.width),
height(other.height),
size(other.size),
bitsPerPixel(other.bitsPerPixel) {}
TGAPicture& operator=(const TGAPicture& other) {
if (this == &other)
return *this;
pixel = other.pixel;
header = other.header;
width = other.width;
height = other.height;
size = other.size;
bitsPerPixel = other.bitsPerPixel;
return *this;
}
TGAPicture& operator=(TGAPicture&& other) noexcept {
if (this == &other)
return *this;
pixel = std::move(other.pixel);
header = std::move(other.header);
width = other.width;
height = other.height;
size = other.size;
bitsPerPixel = other.bitsPerPixel;
return *this;
}
private:
std::vector<RGBA> pixel; //Containes every pixel of the picture
TGAHeader header;
short width, height, size, bitsPerPixel;
...
public:
/**
* Loads and initializes a picture to be used in the program
* #throws TGAExpection if file could not be loaded
*/
static std::unique_ptr<TGAPicture> loadPicture(const std::string& path);
TGAPicture(const std::vector<RGBA>& pixel, const TGAHeader& header);
~TGAPicture();
....
};
}
#endif
cpp:
TGAPicture::TGAPicture(const std::vector<RGBA>& pixel, const TGAHeader& header)
: pixel(pixel),
header(header),
width(header.width),
height(header.height),
size(header.width * header.height * (header.bits / 8)),
bitsPerPixel(header.bits) { }
std::unique_ptr<TGAPicture> TGAPicture::loadPicture(const std::string& path) {
...
for (int i = 0; i < header.height * header.width; i++) {
pixel[i].B = *(bufferPosition++);
pixel[i].G = *(bufferPosition++);
pixel[i].R = *(bufferPosition++);
pixel[i].A = (header.bits > 24 ? *(bufferPosition++) : 0xFF);
}
/**
* Return unique_ptr
* - ObjectFactory
* - Automatic Deletion
*/
return std::unique_ptr<TGAPicture>{new TGAPicture(pixel, header)};
}
And one class with an error would be:
class Model {
public:
explicit Model(const CreatureList& creatureList);
~Model();
Terrain* getTerrain() const;
CreatureList& getCreatureList();
private:
CreatureList creatureList;
Terrain* terrain;
};
Model::Model(const CreatureList& creatureList) : creatureList(creatureList),
terrain(new Terrain()) {
for (CreatureType ty : creatureList) { //line with errror
ty.getInfoInOneLine();
}
}
What do I need to change for it to work? And what would be the optimal way? Pretty sure that I'm supposed to use unique_ptr as return for the TGA::load() method.
I hope you can see through this mess and I'd like to apologize if my English isn't perfect, since it's not my first langugage.
std::unique_ptr is not copyable. It wouldn't be unique anymore if you could copy it.
You create copies of the elements in creatureList in your loop in Model's constructor, but they have a non-copyable member, and so are non-copyable themselves by default. If you don't actually need a copy of the elements, you should use references:
Model::Model(const CreatureList& creatureList)
: creatureList(creatureList),
terrain(new Terrain())
{
for (CreatureType& ty : creatureList) { // changed to reference instead
// Note: this is still working with
// parameter, not the object's
// member.
ty.getInfoInOneLine();
}
}
You haven't actually provided a definition for CreatureList, but I suspect it also isn't copyable. This means that Model::Model's argument can't be copied into the object's member. You have two options to fix this: make sure to move your CreatureList or make it copyable.
std::unique_ptr is movable, which means that CreatureType is by default as well, so you can do something like this:
Model::Model(CreatureList creatureList) // Take by value now
: creatureList(std::move(creatureList)), // Move the parameter to the member
terrain(new Terrain())
{
for (CreatureType& ty : this->creatureList) { // Use this-> to access member
// instead of now moved-from
// parameter. You could also
// just change them to have
// different names.
ty.getInfoInOneLine();
}
}
This changes Model's constructor to take its parameter by value and moves that value into the object's creatureList member.
If it makes sense, you could also make CreatureType copyable by adding an explicit copy constructor that copys the object pointed to by picture:
CreatureType::CreatureType(const CreatureType& other)
: name(other.name),
strengh(other.strengh),
speed(other.speed),
lifespan(other.lifespan),
attributes(other.attributes),
path(other.path),
picture(new TGAPicture(*other.picture))
{
}
If you do that, the implicit move constructor will no longer be generated by the compiler, so you'll want to define that yourself:
CreatureType::CreatureType(CreatureType&& other)
: name(std::move(other.name)),
strengh(other.strengh),
speed(other.speed),
lifespan(other.lifespan),
attributes(std::move(other.attributes)),
path(std::move(other.path)),
picture(std::move(other.picture))
{
}
There doesn't seem to be any reason for TGAPicture::loadPicture to return a std::unique_ptr though. If you just return by value from that function you will avoid all of these problems:
TGAPicture TGAPicture::loadPicture(const std::string& path) {
// ...
return TGAPicture{pixel, header};
}
Related
I want to define a const variable which is part of a class like:
// camera.h
class Camera{
public:
Camera(std::string file);
~Camera() {}
const size_t FRAME_HEIGHT;
const size_t FRAME_WIDTH;
private:
std::string file;
cv::VideoCapture cap;
Read();
};
_____________________________________
// camera.cpp
Camera::Camera(std::string file) : file("settings.yml") {
//...
read();
cap.open(0);
cap.set(CV_CAP_PROP_FRAME_HEIGHT, FRAME_HEIGHT);
cap.set(CV_CAP_PROP_FRAME_WIDTH, FRAME_WIDTH);
}
void Camera::read(){
// read something
}
However this does not work using an initialisation list because I first have to read this data from a settings file.
After calling read() I set my const variables FRAME_HEIGHT and FRAME_WIDTH.
Is it possible to leave them const and if yes how/where should I do that?
It is possible if you use an intermediate class, like this:
class Camera {
struct Reader {
int value;
Reader(bool x) {
// Do whatever you like
value = x ? 42 : 0xDEAD;
}
};
Camera(Reader&& r) : value(::std::move(r.value)) { }
public:
int const value;
template<typename... V>
Camera(V... args)
: Camera(Reader(std::forward<V>(args)...))
{ }
};
This effectively adds another stage to the initialization, but still keeps encapsulation (Reader and the corresponding constructor are private to Camera!). The performance impact should be negligible (forward and move operations are usually cheap), unless you initialize millions of these objects in some tight inner loop.
One option is to initialize a settings object from a file, and take needed values from it:
class Camera
{
// note this section is moved to the beginning,
// so file and settings are initialized before FRAME_HEIGHT and FRAME_WIDTH
private:
std::string file;
Settings settings;
public:
Camera(std::string file)
: file(file)
, settings(file)
, FRAME_HEIGHT(settings.getFrameHeight())
, FRAME_WIDTH(settings.getFrameWidth())
{
}
~Camera() {}
const size_t FRAME_HEIGHT;
const size_t FRAME_WIDTH;
};
Also it could make sense to rewrite Camera constructor after that as
Camera(const Settings& settings)
: FRAME_HEIGHT(settings.getFrameHeight())
, FRAME_WIDTH(settings.getFrameWidth())
{
}
-- this way you can take settings from anywhere, not only from a file.
I have a class that contains an array of object pointers as its member variable. I'm currently having an issue in getting the compiler to copy an object to the end of the array as when I step through the program the array of objects reads that its memory cannot be read. Anyone know what the issue might be?
void Notifications::operator+=(const iMessage& src) {
iMessage** temp2 = nullptr;
temp2 = new iMessage*[size+1];
if (size != 0){
for (int i = 0; i < size; i++) {
*temp2[i] = *messages[i];
}
}
*temp2[size] = src; //compiler states that it cannot read the data from temp2 after this point
delete[]messages;
for (int i = 0; i < size + 1; i++) {
*messages[i] = *temp2[i]; //Unhandled exception at 0x00C58F99 in w5.exe: 0xC0000005: Access violation reading location 0x00000000.
}
size++;
}
Notifications.h
#include "iMessage.h"
#include <vector>
namespace w5 {
class Notifications {
int size;
iMessage **messages;
public:
Notifications();
Notifications(const Notifications&);
Notifications& operator=(const Notifications&);
Notifications(Notifications&&);
Notifications&& operator=(Notifications&&);
~Notifications();
void operator+=(const iMessage&);
void display(std::ostream&) const;
};
}
IMessage.h
#ifndef _I_MESSAGE_H_
#define _I_MESSAGE_H_
// Workshop 5 - Containers
// iMessage.h
#include <iostream>
#include <fstream>
namespace w5 {
class iMessage {
public:
virtual void display(std::ostream&) const = 0;
virtual iMessage* clone() const = 0;
virtual bool empty() const = 0;
};
iMessage* getMessage(std::ifstream&, char);
}
#endif
Message.h
#include "iMessage.h"
namespace w5{
class Twitter : public iMessage {
std::string msg;
public:
Twitter(char, std::ifstream&);
virtual void display(std::ostream&) const;
virtual iMessage* clone() const;
virtual bool empty() const;
};
class Email : public iMessage {
std::string msg;
public:
Email(char, std::ifstream&);
virtual void display(std::ostream&) const;
virtual iMessage* clone() const;
virtual bool empty() const;
};
}
1) Just use vector.
2) You should always post exact compiler messages. "compiler states that it cannot read the data from temp2 after this point" is not good enough.
3) You allocate an array of pointers, and then dereference those pointers, but you never let the pointers point anywhere.
4) You delete the messages array and then proceed to copy back into it as if it was still there. (What you actually want to do is just assign messages = temp2.)
5) You're slicing objects all over the place, by using assignment to attempt to copy iMessage objects. There's a reason iMessage has a clone() function.
First you do
delete[]messages;
then you do
*messages[i] = *temp2[i];
attempting to access the array you've just deleted. I think you just want to take the pointer to the array you've just created:
messages = temp2;
You also do
*temp2[size] = src;
when temp2[size] doesn't point to anything. That should probably be
temp2[size] = src.clone();
to make a persistent copy of the argument and store it in the array.
It's rather tricky to follow this weird pointer-juggling; I think you also want to delete each element of messages before messages itself to avoid leaks. Why not just use std::vector to take care of memory allocation for you? That will reduce the whole insane dance to
std::vector<std::unique_ptr<iMessage>> messages;
void operator+=(const iMessage & src) {
messages.emplace_back(src.clone());
}
Also, _I_MESSAGE_H_ is a reserved name. You should remove the leading underscore.
You want to convert a const reference into a non-const pointer.
I wonder that the compiler doesn't throw errors. Which compiler you use?
Is something like this not possible?
void Notifications::operator+=(iMessage* src) {
I was not testing but this should also work:
void Notifications::operator+=(iMessage& src) {
*bar[foo] = &src;
I have a message class that I decided to use the builder design pattern. Each message, when completely built, looks very similar. I use a std::string to hold the information (its actually just independent chars, so I could have used vector<char> but the .c_str() was convenient.
The method of construction of each different subtype of message is the same (build header, build cargo, build footer, calc checksum... this is defined in the MessageBuilder class (and inherited to custom message builder classes):
class MessageBuilder
{
public:
// implementation details for all messages
static const int32 MsgDelimeter = 99;
// ...
Message getMsg();
void buildMessage();
protected:
MessageBuilder(uint8 msgID, uint8 cargoLen, uint8 csi, const uint8* cargo, const uint8 length)
: m_msgID(msgID), m_cargoLen(cargoLen), m_csi(csi),
m_cargo(cargo), m_contents(""), m_msg(m_contents)
{ }
// I previously tried passing cargo and length as just a std::string
// if I could get that to work it would also be fine
void buildHeader();
void buildCargo();
void buildFooter();
void resizeContents();
void calculateCheckSum();
// message is built in m_contents
Message::string m_contents;
Message::string m_cargo;
Message m_msg;
// variables specific to msg type
uint8 m_msgID;
uint8 m_cargoLen;
uint8 m_csi;
private:
};
Then to build a specific message, I have a specific class:
class CustomMessageABuilder : public MessageBuilder
{
public:
static const uint8 CustomMessageAID = 187;
// more details
// ...
// what I want to do
// static const uint8 CustomMessageACargo[4] = {0x65, 0xc7, 0xb4, 0x45};
// ** HERE **
CustomMessageABuilder ()
: MessageBuilder(CustomMessageAID,
CustomMessage1CargoLen,
//...
CustomMessageACargo,
CustomMessageALength
{ }
};
Anyway, what I want to do is pass the only custom string of characters, the cargo, from the CustomMessageABuilder constructor to the MessageBuilder class, where it will be stored in the middle of the message.
The cargo is different for each message, but gets stored in the same way, so all the logic for storing it/creating the cargo is in the base MessageBuilder class. All the differences, like msgID, cargoLen, cargo, ... are constants in the CustomMessageBuilder classes.
This would allow me to keep my message class really simple:
class Message
{
public:
typedef std::string string;
// ctor
Message(string);
// dtor
~Message();
// copy ctor
Message(const Message&);
// assignment operator
Message& operator=(const Message&);
// getters
uint8 getLength() const;
const string& getData() const;
const uint8* getCSTR() const;
// setters
void setData(const string&);
protected:
// ctor
Message() : m_contents("") { }
// contents of entire message
string m_contents;
};
So I guess it all boils down to this:
What is the best way to define a constant array of characters/hex values (each message cargo) for a class, (and still be able to pass it in the initialization list of the constructor)? Or, tell me the obvious way to do this that I am missing.
Note: For other message classes, the cargo will be dynamic content, but always fixed length.
Note2: I will eventually have a director class which will own a CustomMessageBuilder() and tell it to buildMessage().
Any help, advice, criticism etc would be much appreciated.
Static const members can be initialized outside of the class.
#include <iostream>
class A
{
public:
static const char cargo[4];
};
const char A::cargo[4] = {0x65, 0xc7, 0xb4, 0x45};
int main()
{
std::cout << A::cargo[0] << A::cargo[1] << A::cargo[2] << A::cargo[3] << std::endl;
}
I have a relationship between two classes and some additional functional code illustrated in the below example. The MiddleMan class holds a couple of containers of pointers to DataObject class instances. I want to enforce read-only access to the data objects in one container, while allowing write access to the data containers in the other. Users of MiddleMan should never be able to modify the containers themselves directly (the ptr_vector members).
I guess there is const promotion happening to the DataObject pointers when accessed through the MiddleMan const member function. How can I avoid this? In my object relationship, MiddleMan does not logically own the DataObject instances. Thus, I do not want the DataObject instances const-protected.
I have used the boost::ptr_vector container as I understand the STL containers can be more problematic for this sort of thing. Didn't solve my problem though.
I am happy to use const_cast in MiddleMan, but I don't see how to.
// const correctness access through middleman example
// g++ -I/Developer/boost ptr_container_ex.cc
#include <boost/ptr_container/ptr_vector.hpp>
struct DataObject
{
DataObject(size_t n) : _data(new float[n]) {}
~DataObject() {delete _data;}
float * dataNonConst() { return _data; }
const float * dataConst() const { return _data; }
float * _data;
};
struct MiddleMan
{
typedef boost::ptr_vector<DataObject> containerVec_t;
const containerVec_t & inputVars() const { return _inputVars; }
const containerVec_t & outputVars() const { return _outputVars; }
void addInputVar( DataObject * in ) { _inputVars.push_back( in ); }
void addOutputVar( DataObject * out ) { _outputVars.push_back( out ); }
containerVec_t _inputVars, _outputVars;
};
// just an example that the DataObject instances are managed externally
DataObject g_dataInstances[] = {DataObject(1), DataObject(2), DataObject(3)};
MiddleMan theMiddleMan;
int main()
{
theMiddleMan.addInputVar( &g_dataInstances[0]); // this is just setup
theMiddleMan.addOutputVar( &g_dataInstances[1]);
const MiddleMan & mmRef = theMiddleMan; // I actually only have a const ref to work with
// read data example
const MiddleMan::containerVec_t & inputs = mmRef.inputVars();
float read = inputs[0].dataConst()[0];
// write data example
const MiddleMan::containerVec_t & outputs = mmRef.outputVars();
float * data_ptr = outputs[0].dataNonConst(); // COMPILER ERROR HERE:
return 0;
}
I'm getting the compiler output:
ptr_container_ex.cc: In function ‘int main()’:
ptr_container_ex.cc:49: error: passing ‘const DataContainer’ as ‘this’ argument of ‘float* DataContainer::dataNonConst()’ discards qualifiers
You need to exclude const modifiers every place you are not going to have const access:
containerVec_t & outputVars() { return _outputVars; } //Change definition in MiddleMan class
MiddleMan::containerVec_t & outputs = theMiddleMan.outputVars(); //Change reference type
float * data_ptr = outputs[0].dataNonConst(); //No compiler error here
One working scenario uses const_cast in accessing the outputVars. I'm not able to return a reference to the whole container, but I can get the functionality I need returning begin and end iterators.
// const correctness access through middleman example
// g++ -I/Developer/boost ptr_container_ex.cc
#include <boost/ptr_container/ptr_vector.hpp>
struct DataObject
{
DataObject(size_t n) : _data(new float[n]) {}
~DataObject() {delete _data;}
float * dataNonConst() { return _data; }
const float * dataConst() const { return _data; }
float * _data;
};
struct MiddleMan
{
typedef boost::ptr_vector<DataObject> containerVec_t;
containerVec_t::iterator outputVarsBegin() const { return const_cast<containerVec_t&>(_outputVars).begin(); }
containerVec_t::iterator outputVarsEnd() const { return const_cast<containerVec_t&>(_outputVars).end(); }
const containerVec_t & inputVars() const { return _inputVars; }
void addInputVar( DataObject * in ) { _inputVars.push_back( in ); }
void addOutputVar( DataObject * out ) { _outputVars.push_back( out ); }
containerVec_t _inputVars, _outputVars;
};
// just an example that the DataObject instances are managed externally
DataObject g_dataInstances[] = {DataObject(1), DataObject(2), DataObject(3)};
MiddleMan theMiddleMan;
int main()
{
theMiddleMan.addInputVar( &g_dataInstances[0]); // this is just setup
theMiddleMan.addOutputVar( &g_dataInstances[1]);
const MiddleMan & mmRef = theMiddleMan; // I actually only have a const ref to work with
// read data example
const MiddleMan::containerVec_t & inputs = mmRef.inputVars();
float read = inputs[0].dataConst()[0];
// write data example
float * data_ptr2 = mmRef.outputVarsBegin()->dataNonConst(); // WORKS
return 0;
}
I want to create an immutable data structure which, say, can be initialized from a file.
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
}
};
What I could do to fix the problem is
class Image {
MetaData md;
public:
const int width,height;
Image(const char *filename):
md(readDataFromFile(filename)),
width(md.width()),height(md.height()) {}
};
However
It forces me to save MetaData as a field in my object. Which I don't always want.
Sometimes the logic in the constructor is much more complex than a single read (say, error handling can take a few lines)
So the only solution I thought of is along the lines of
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 */{}
};
Is there a better idea? (In Java, you're allowed initializing finals in the constructor).
You could move width and height into one type and move the initialization code into an initialization helper function:
// 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)) {}
You can simply use the NamedConstructor idiom here:
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;
};
One of the main advantage of Named Constructors is their obviousness: the name indicates you are building your object from a file. Of course it's slightly more verbose:
Image i = Image::FromFile("foo.png");
But that never troubled me.
If it was C++0x, I would recommend this (delegating constructors):
class Image
{
public:
const int width, height;
Image(const char* filename) : Image(readDataFromFile(filename)) { }
Image(const MetaData& md) : width(md.width()), height(md.height()) { }
};
You should add inline getters for the width and height instead of public const member variables. The compiler will make this solution as fast as the original try.
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.: I used to write private things at the end because they are less important for the user of the class.
First, you should understand the constructor body is just for running code to complete initializing your object as a whole; the members must be completely initialized before the body is entered.
Ergo, all members are initialized in an (implicit unless made explicit) initialization list. Clearly, const variables must be initialized in the list because once you enter the body, they are already suppose to be initialized; you'd simply be trying to assign them.
Generally, you don't have const members. If you want those members to be immutable, just don't give any public access to them that could change them. (Also, having const members make your class non-assignable; typically unnecessarily.) Going this route easily fixes your problem, as you'd just assign them values in the body of the constructor like you wish.
A method to do what you want while maintaining const could be:
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)))
{}
};
I don't think this route is worth it.
You could cast away the constness in the constructor:
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";
}
};
That would achieve what you want to do but I must say I personally would stay away from that because it causes undefined behavior according to the standard (excerpt from cppreference)
const_cast makes it possible to form a reference or pointer to
non-const type that is actually referring to a const object ...
Modifying a const object through a non-const
access path ... results in undefined behavior.
I would fear any public data members(at least in regarding your particular example). I would go with Georg's approach or make the data private and provide only the getter.
How about passing MetaData as an argument to the constructor. This gives a lot of benefits:
a) The constructor interface makes it clear about the dependency on MetaData.
b) It facilitates testing of the Image class with different types of MetaData (subclasses)
So, I would probably suggest similar to follows:
struct MD{
int f(){return 0;}
};
struct A{
A(MD &r) : m(r.f()){}
int const m;
};
int main(){}
I'd use a static method:
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;
}
As far my understanding i think this mechanism will work.
This is one of my least favorite aspects of C++ when compared to Java. I'll use an example I was working on when I needed to solve this problem.
What follows is the equivalent of a readObject method. It deserializes a Video key from a provided file path.
#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);
}