I know this sounds like a strange question, but bear with me.
I have a custom class that has some large objects which need to be returned by reference to avoid a copy.
My class looks something like this:
class csv_File {
public:
csv_File(std::string); //constructor
std::string const& access(int,int) const;
void modify_element(int column,int row ,std::string value) {
storage.at(row).at(column)=value;
}
private:
mutable std::vector < std::vector<std::string> > storage;
};
The code for access is:
string const& csv_File::access(int column,int row) const {
return storage.at(row).at(column);
}
When I try to compile this, I get an error, as it wants
csv_File::modify_element(int column,int row ,std::string value) const {}
In practice, storage is logically const, but I just need to be able to modify it once in a while. Is there a way to do this?
Also, a related question. If I call modify_element to modify an element in storage, but previously, I have returned a reference to that element using access, will the reference that was returned previously pick up the new value after the modify_element call?
After fixing up your program, and with the hint from #user763305, I suppose you have something like this (note: this program compiles, but invokes undefined behavior when run, since the storage vector is left empty):
#include <string>
#include <vector>
class csv_File {
public:
csv_File(std::string) {}
std::string const& access(int,int) const;
void modify_element(int column,int row ,std::string value) {
storage.at(row).at(column)=value;
}
private:
mutable std::vector < std::vector<std::string> > storage;
};
std::string const& csv_File::access(int column,int row) const {
return storage.at(row).at(column);
}
const csv_File& Factory() { static csv_File foo("foo.txt"); return foo; }
int main(){
const csv_File& f(Factory());
f.modify_element(1, 2, "hello");
}
and you get an error like this:
cs.cc: In function ‘int main()’:
cs.cc:24: error: passing ‘const csv_File’ as ‘this’ argument of ‘void csv_File::modify_element(int, int, std::string)’ discards qualifiers
So, you have a const object and are trying to invoke modify_element on it. Logically, this is a mistake -- you can't modify const objects!
You have, as I see it, two choices for a fix. Either you can declare the vector object mutable and the modify_element method const, or you can modify your object factory to return non-const references.
Solution 1:
...
void modify_element(int column,int row ,std::string value) const {
storage.at(row).at(column)=value;
}
...
Solution 2:
...
const csv_File& Factory() { static csv_File foo("foo.txt"); return foo; }
csv_File& RW_Factory() { static csv_File foo("foo.txt"); return foo; }
...
const csv_File& f(RW_Factory());
...
EDIT
And, yes, if you previously returned a reference to a string in your vector, and you subsequently assign a new value to that string as you do in modify_element, then the previous reference will reflect the new value.
Note that the previous reference will be invalid if you destroy the vector, erase that vector element, or do anything to cause the vector to grow beyond its previous capacity.
Related
I have a function that should return either a new vector or a reference to an existing one. I need this because in some situations the vector is already created and owned by somebody else and I want to avoid copying it.
One option is to return std::variant<std::vector<int>, std::vector<int>&>, but then the caller needs to add logic to discern what was returned. (NOT ALLOWED)
Another option is to use a wrapper class (I avoid templates for clarity):
class VectorContainer {
VectorContainer() : v(std::vector<int>()), v_ptr(nullptr) {}
VectorContainer(std::vector<int>& ref): v_ptr(&ref) {}
std::vector<int>& get() {
if (v_ptr == nullptr) return v;
return *v_ptr;
}
private:
std::vector<int> v;
std::vector<int>* v_ptr;
};
VectorContainer f();
The reference is guaranteed to outlive VectorContainer and additionally, the code that owns the vector is fixed and you cannot change it. I believe this disallows using something like a shared pointer.
Is there an existing class in the standard library? If not, how can I do this?
Your approach is close, but I would suggest having the reference always refer to the proper object:
class VectorContainer {
private:
std::vector<int> v_own;
public:
std::vector<int>& data;
VectorContainer(std::vector<int> own) : v_own{std::move(own)}, data{v_own} {}
VectorContainer(std::vector<int>& ref) : v_own{}, data{ref} {}
~VectorContainer() = default;
VectorContainer& operator=(VectorContainer const&) = delete;
VectorContainer& operator=(VectorContainer&&) = delete;
VectorContainer(VectorContainer const& from) :
v_own{from.v_own},
data{(&from.v_own == &from.data)?v_own:from.data} {
}
VectorContainer(VectorContainer&& from) :
v_own{std::move(from.v_own)},
data{(&from.v_own == &from.data)?v_own:from.data} {
}
};
Here you always access data or you could hide data and add a wrapper like this:
std::vector<int>& operator*() const {
return data;
}
// or a conversion operator to std::vector<int>&
Either way, whether or not there is indirection is hidden from the user. You always see a reference. Note that lugging around an empty vector has virtually no overhead. The initialisation is minimal.
Example:
struct Example {
std::vector<int> v{1,2,3};
VectorContainer example(bool const b) {
if (b)
return VectorContainer(std::vector{0}); // new vector
else
return VectorContainer(v); // reference to v
}
};
I have class that resembles this:
class A{
std::string tmp;
public:
const std::string &doSomething() const{
tmp = "something";
return tmp;
}
};
It is very important the method doSomething() to be const and to return reference instead of the string itself.
Only way I see is to do it with dynamic allocation, e.g. something like:
class A{
MyClass *tmp = new MyClass();
public:
const MyClass &doSomething() const{
*tmp = "something";
return *tmp;
}
};
tmp variable "placeholder" is used ONLY inside doSomething().
Is there some other, clear, better way to do this kind of temp value storage?
You can use mutable modifier on std::string tmp:
class A {
mutable std::string tmp;
...
}
This would allow a const method to modify that member.
Check out the mutable keyword for declaring tmp.
If you try to modify the attribute, you should quit the const qualifier of the method signature:
std::string& doSomething() { ... }
If you don't want to modify it, and you want to ensure that the method returns whatever you are waiting to receive:
const std::string& doSomething() const {...}
returning a const reference is the best way to ensure that the reference doesn't change of value. But without the const qualifier before the return type also should work well because of the second const qualifier (which specifies that the method shouldn't modify the current object)
In conclusion, I'm completely agree with #juanchopanza
Cheers
I am trying to pass a STL container in a class to another class
For ex.
typedef std::map<std::string, int*> Container_t;
class A
{
public:
const Container_t * get_container() const;
private:
Container_t container;
};
const Container_t * A::get_container() const
{
return &container;
}
And when I try to get this in another class, I get compilation error
saying
error: argument of type ‘Container_t* (A::)()const’ does not match ‘Container_t*’
void foo(A * a)
{
const Container_t * container = a->get_container();
}
It will be best if I can get const reference instead of pointer. but I don't want to copy the return value of function in A. So it has to be either pointer or reference. Thank you
You need to change your function signature to:
const Container_t * get_container() const;
The reason is that the last const there at the end says "calling this function and using what it returns will not change this object." However, returning a (non-const) pointer to the member variable gives the caller freedom to change that variable, so you have to return a const Container_t * to ensure that the caller doesn't modify the member variable (and thereby modify the object).
I'm trying to understand better how does const-correctness work and more specifically, when dealing with classes whose members are based on containers and smart pointers.
I guess that the const-correctness property is the same regardless of the class members. However, since I'm having some difficulties to clearly understand what's going on,
I decided to ask you for advice.
So, here is the context. I've a ShapeContainer class that has as private class member a vector of smart pointers.
The Shape class is abstract and has the following virtual function virtual float doSomething(); which is then redefined by its derived classes. Note that it's a non-const class function.
The relevant part of the code is given below:
class ShapeContainer{
public:
typedef std::shared_ptr<Shape> ShapePtr;
typedef std::vector<ShapePtr> ShapePtrContainer;
// .......
const ShapePtr & operator[]( int ) const { return m_vect[index]; }; // const version
// ShapePtr & operator[]( int ) { return m_vect[index]; }; // non-const version
// .......
private:
ShapePtrContainer m_vect;
};
class Shape{
public:
// ...
virtual float doSomething() = 0;
};
Here are my questions.
Q1. Why do I'm allowed to call the doSomething() function in the following way: int index = 0; float tmp = container1[index]->doSomething(); (having ShapeContainer container1=createBasicShapes();)?
From what I understand, after calling to the const ShapePtr operator[] const function we'll get a const pointer to a Shape object, however the doSomething() virtual
function is not const. So, how does a reference to a const-object can call a non-const function?
Q2. By calling the doSomething() function as previouly ilustrated (float tmp =container1[index]->doSomething();) and by adding a non-const version of the operator[], this latter
overloaded version is then called instead of the const-version one. Why does it is so?
Now, instead of having a ShapeContainer class, I've now a new class named ShapeContainerInfo that still has a vector but of an intermediate ShapeInfo class (that has a smart pointer as a class member).
class ShapeContainerInfo{
public:
typedef std::vector<ShapeInfo> ShapeContainer;
const ShapeInfo & operator []( int index) const { return m_vect[index]; };
// ShapeInfo & operator []( int index) { return m_vect[index]; }; // non-const version
private:
ShapeContainer m_vect;
};
class ShapeInfo{
public:
typedef std::shared_ptr<Shape> ShapePtr;
// ...
float doSomething(){ return m_ShapePtr->doSomething(); };
private:
ShapePtr m_ShapePtr;
int m_nID;
};
Q3. When I call float tmp = container2[i].doSomething();, I get the following compiler error: error C2662: 'ShapeInfo::doSomething' : cannot convert 'this' pointer from 'const ShapeInfo' to 'ShapeInfo &'.
However, when I add a non-const vesion of the overloaded operator [] the compiler error is gone. So, why do I really need the non-const operator[] for ShapeContainerInfo and not for ShapeContainer?
Q4. If the m_vect private member of ShapeContainerInfo is set now as public member and only the const-version of operator[] is defined (not the non-const one), there are no compiler error messages. Why this? e.g. after setting m_vect to be a public class member: float tmp = info.m_vect[i].doSomething();
Q5. How could I correctly define both the ShapeInfo and ShapeContainerInfo classes such that I only need to define the const-version of the operator[] and still being able to call the float doSomething() function?
For those of you interested in the whole sample code, please find it here.
Clarifications, suggestions are always welcomed :-)
Merci!
Q1: The shared_ptr is const, that doesn't mean that the pointed to object is const. For that you would want shared_ptr<const Shape>.
Q2: Since you're ShapeContainer was not const, the non-const function was a better match, so it was called instead of the const version.
Q3: vector propagates its constness to its elements. shared_ptr does not. This is inline with the behavior of arrays and raw pointers. The elements of const arrays are const. The thing pointed to by a const pointer is not (necessarily) const.
Q4: Are you saying this produces no error?
ShapeContainerInfo info;
info[0].doSomething();
Please clarify, because that should be an error.
Q4: Okay, so you're saying that this produces no error:
ShapeContainerInfo info;
info.m_vect[0].doSomething();
Nor should it. The vector is not const. It's only inside the const member function that the vector(and all other members) are treated as const.
Q5: Make m_vect a vector of unique pointers. Inside the const function, the vector itself will be const, and the unique pointers will be const. But the objects that the unique pointers point to will be mutable.
As an example, the set function in this class is not legal:
struct Foo
{
void set(int index, int value) const
{
v[index] = value;
}
std::vector<int> v;
};
But this one is:
struct Foo
{
void set(int index, int value) const
{
*v[index] = value;
}
std::vector<std::unique_ptr<int>> v;
};
I'm looking at some code at the moment which has been ported and is failing to compile. The code has been written in a rather 'C' like way and is passing function pointers in order to set particular mutators on an object. The object being populated is declared as follows:
class Person
{
std::string n_;
int a_;
public:
void name( const std::string& n ) { n_ = n; }
std::string name() const { return n_; }
void age( const int& a ) { a_ = a; }
int age() const { return a_; }
};
Fairly standard stuff. Then we have some interesting functions which I've trimmed for brevity:
typedef void (Person::FnSetStr)(const std::string& s);
typedef void (Person::FnSetInt)(const int& i);
void setMem( const std::string& label, Person* person, FnSetStr fn)
{
// Do some stuff to identify a std::string within a message from the label.
// assume that 'val_s' contains the string value of the tag denoted by
// the label.
(person->*fn)(val_s);
}
void setMem( const std::string& label, Person* person, FnSetInt fn)
{
// Do some stuff to identify an int within a message from the label.
// assume that 'val_i' contains the int value of the tag denoted by the
// label.
(person->*fn)(val_i);
}
And then this gets called as follows:
Person* person = new Person;
setMem("Name", person, Person::name ); // (1)
setMem("Age", person, Person::age ); // (2)
The idea seems to be to pass a label, an object and the address of an appropriate mutator. The type of the 3rd parameter is being used to get the compiler to select which overload to call and the specific overload then gets a suitable variable ready and calls the function passing it as a parameter to set the value on the object.
This workled on an old Solaris compiler. However, when it compiles on GCC, I get failures at points (1) and (2):
error: no matching function for call to
'setMem( const std::string& label, Person* person, <unknown type> )'
It looks like the new compiler treats, say, Person::age as a type rather than a pointer to a function and cannot resolve the overload. I am looking at changing the code to use a function object rather than straight pointers to functions.
I wanted to know whether there's a way that the calling code can stay like this (i.e. without an explicitly stating the type that the function takes) bearing in mind that I can't change the Person class and would ideally like to keep changes to a minimum.
First change the declaration:
typedef void (Person::*FnSetStr)(const std::string& s);
typedef void (Person::*FnSetInt)(const int& i);
Then change the call:
setMem("Name", person, &Person::name ); // (1)
setMem("Age", person, &Person::age ); // (2)
Builds clean at warning level 4 in VS 2010.