I want to make my collection immutable outside of my class.
from this:
public:
vector<int>& getValues(){
return values;
}
private:
vector <int>& values;
to this:
public:
vector<int> getValues(){
return values;
}
private:
vector <int>& values;
Will it work fine?
It will work, but you would be better off returning a const reference:
const vector<int> & getValues() const {
return values;
}
Also, storing a reference in a class is often (not always) a mistake. You probably want a value, or possibly a pointer.
Yes, but it might have a negative impact on performance, if the collection is large, and the function is called often. What's wrong with just returning a vector<int> const& (and declaring getValues() const)?
Also, I'm wondering about the fact that your member is a reference. Members should rarely be references.
public:
vector<int> getValues(){
return values;
}
private:
vector <int>& values;
Cannot possibly work as is, what is the reference to your vector actually referencing? Unless you're not showing all code here, it's not gonna work.
Just have a vector<int> values; instead.
Yes, you can do that, but this is not necessarily what you want.
By doing this, your getValues method will make a copy of the vector. This will take some time (depending on the size of the vector).
If you are sure that the vector is not frequently changed, why not return a const reference, like this:
const vector<int> &getValues() {return values;}
return a pair of const_iterators, then the callee is abstracted from the underlying container too...
e.g.
typedef std::vector<int> vector_type;
public:
typedef std::pair<vector_type::const_iterator, vector_type::const_iterator> range;
range getValues() const
{
return range(values.begin(), values.end());
}
private:
vector_type values;
Related
I have a map of objects where keys are std::string. How can I generate a vector of keys without copying the data?
Do I need to change my map to use std::shared_ptr<std::string> as keys instead? Or would you recommend something else?
My current code goes like this:
MyClass.h
class MyClass {
private:
std::map <std::string, MyType> my_map;
public:
const std::vector<std::string> MyClass::getKeys() const;
}
MyClass.cpp
const std::vector<std::string> MyClass::getKeys() const
{
std::vector<std::string> keys = std::vector<std::string>();
for (const auto& entry : my_map)
keys.push_back(entry.first); // Data is copied here. How can I avoid it?
return keys;
}
As suggested by Kevin, std::views::keys was pretty much made for this. The view it produces is a lightweight object, not much more than a pointer to the range argument, which solves the ownership and lifetime issues. Iterating over this view is identical to iterating over the map, the only difference is that the view's iterator dereferences to the first element of the underlying map value.
As for some code, it is pretty simple:
// ...
#include <ranges>
class MyClass {
private:
std::map<std::string, MyType> my_map;
public:
std::ranges::view auto getKeys() const;
};
std::ranges::view auto MyClass::getKeys() const
{
return std::views::keys(my_map);
}
Since ranges and concepts go hand in hand, I've used the std::ranges::view to constrain the auto return type. This is a useful way to let the compiler and user of your function know that it will return a particular category of object, without having to specify a potentially complicated type.
Lets show it in an example where we have a Data class with primary data, some kind of index that points to the primary data, and we also need to expose a const version the index.
class Data
{
public:
const std::vector<int>& getPrimaryData() const { return this->primaryData; }
const std::vector<int*>& getIndex() const { return this->index; }
private:
std::vector<int> primaryData;
std::vector<int*> index;
};
This is wrong, as the user can easily modify the data:
const Data& data = something.getData();
const std::vector<int*>& index = data.getIndex();
*index[0] = 5; // oups we are modifying data of const object, this is wrong
The reason of this is, that the proper type the Data::getIndex should be returning is:
const std::vector<const int*>&
But you can guess what happens when you try to write the method that way to "just convert the non-const variant to const variant":
// compiler error, can't convert std::vector<int*> to std::vector<const int*> these are unrelated types.
const std::vector<const int*>& getIndex() const { return this->index; }
As far as I know, C++ doesn't have any good solution to this problem. Obviously, I could just create new vector, copy the values from the index and return it, but that doesn't make any sense from the performance perspective.
Please, note that this is just simplified example of real problems in bigger programs. int could be a bigger object (Book lets say), and index could be index of books of some sort. And the Data might need to use the index to modify the book, but at the same time, provide the index to read the books in a const way.
In C++20, you can just return a std::span with elements of type const int*
#include <vector>
#include <span>
class Data
{
public:
std::span<const int* const> getIndex() const { return this->index; }
private:
std::vector<int*> index;
};
int main() {
const Data data;
const auto index = data.getIndex();
*index[0] = 5; // error: assignment of read-only location
}
Demo
Each language has its rules and usages... std::vector<T> and std::vector<const T> are different types in C++, with no possibility to const_cast one into the other, full stop. That does not mean that constness is broken, it just means it is not the way it works.
For the usage part, returning a full container is generally seen as a poor encapsulation practice, because it makes the implementation visible and ties it to the interface. It would be better to have a method taking an index and returning a pointer to const (or a reference to a pointer to const if you need it):
const int* getIndex(int i) const { return this->index[i]; }
This works, because a T* can be const_casted to a const T *.
The top answer, using ranges or spans, is a great solution, if you can use C++20 or later (or a library such as the GSL). If not, here are some other approaches.
Unsafe Cast
#include <vector>
class Data
{
public:
const std::vector<const int>& getPrimaryData() const
{
return *reinterpret_cast<const std::vector<const int>*>(&primaryData);
}
const std::vector<const int* const>& getIndex()
{
return *reinterpret_cast<const std::vector<const int* const>*>(&index);
}
private:
std::vector<int> primaryData;
std::vector<int*> index;
};
This is living dangerously. It is undefined behavior. At minimum, you cannot count on it being portable. Nothing prevents an implementation from creating different template overloads for const std::vector<int> and const std::vector<const int> that would break your program. For example, a library might add some extra private data member to a vector of non-const elements that it doesn’t for a vector of const elements (which are discouraged anyway).
While I haven’t tested this extensively, it appears to work in GCC, Clang, ICX, ICC and MSVC.
Smart Array Pointers
The array specialization of the smart pointers allows casting from std::shared_ptr<T[]> to std::shared_ptr<const T[]> or std::weak_ptr<const T[]>. You might be able to use std::shared_ptr as an alternative to std::vector and std::weak_ptr as an alternative to a view of the vector.
#include <memory>
class Data
{
public:
std::weak_ptr<const int[]> getPrimaryData() const
{
return primaryData;
}
std::weak_ptr<const int* const[]> getIndex()
{
return index;
}
private:
std::shared_ptr<int[]> primaryData;
std::shared_ptr<int*[]> index;
};
Unlike the first approach, this is type-safe. Unlike a range or span, this has been available since C++11. Note that you would not actually want to return an incomplete type with no array bound—that’s just begging for a buffer overflow vulnerability—unless your client knew the size of the array by some other means. It would primarily be useful for fixed-size arrays.
Subranges
A good alternative to std::span is a std::ranges::subrange, which you can specialize on the const_iterator member type of your data. This is defined in terms of a begin and end iterator, rather than an iterator and size, and could even be used (with modification) for a container with non-contiguous storage.
This works in GCC 11, and with clang 14 with -std=c++20 -stdlib=libc++, but not all other compilers (as of 2022):
#include <ranges>
#include <vector>
class Data
{
private:
using DataType = std::vector<int>;
DataType primaryData;
using IndexType = std::vector<DataType::pointer>;
IndexType index;
public:
/* The types of views of primaryData and index, which cannot modify their contents.
* This is a borrowed range. It MUST NOT OUTLIVE the Data, or it will become a dangling reference.
*/
using DataView = std::ranges::subrange<DataType::const_iterator>;
// This disallows modifying either the pointers in the index or the data they reference.
using IndexView = std::ranges::subrange<const int* const *>;
/* According to the C++20 standard, this is legal. However, not all
* implementations of the STL that I tested conform to the requirement that
* std::vector::cbegin is contstexpr.
*/
constexpr DataView getPrimaryData() const noexcept
{
return DataView( primaryData.cbegin(), primaryData.cend() );
}
constexpr IndexView getIndex() const noexcept
{
return IndexView( index.data(), index.data() + index.size() );
}
};
You could define DataView as any type implementing the range interface, such as a std::span or std::string_view, and client code should still work.
You could return a transforming view to the vector. Example:
auto getIndex() const {
auto to_const = [](int* ptr) -> const int* {
return ptr;
};
return this->index | std::views::transform(to_const);
}
Edit: std::span is simpler option.
If index contains pointers to elements of primaryData, then you could solve the problem by instead storing integers representing the indices of the currently pointed objects. Anyone with access to non-const primaryData can easily turn those indices to pointers to non-const, others cannot.
primaryData isn't stable,
If primaryData isn't stable, and index contains pointers to primaryData, then the current design is broken because those pointers would be invalidated. The integer index alternative fixes this as long as the indices remain stable (i.e. you only insert to back). If even the indices aren't stable, then you are using a wrong data structure. Linked list and a vector of iterators to the linked list could work.
You're asking for std::experimental::propagate_const. But since it is an experimental feature, there is no guarantee that any specific toolchain is shipped with an implementation. You may consider implementing your own. There is an MIT licensed implementation, however. After including the header:
using namespace xpr=std::experimental;
///...
std::vector<xpr::propagate_const<int*>> my_ptr_vec;
Note however that raw pointer is considered evil so you may need to use std::unique_ptr or std::shared_ptr. propagate_const is supposed to accept smart pointers as well as raw pointer types.
As mentioned in a comment, you can do this:
class Data
{
public:
const std::vector<int>& getPrimaryData() const { return this->primaryData; }
const std::vector<const int*>& getIndex() const { return this->index; }
private:
std::vector<int> primaryData;
std::vector<const int*> index;
int* read_index_for_writing(std::size_t i) { return const_cast<int*>(index[i]); }
};
Good things about this solution: it works, and is safe, in every version of the standard and every compliant implementation. And it returns a vector reference with no funny wrapper classes – which probably doesn't matter to the caller, but it might.
Bad: you have to use the helper method internally, though only when reading the index for the purpose of writing the data. And the commenter described it as "dirty", but it seems clean enough to me.
Prepare the type like following, and use as return type of Data::getIndex().
class ConstIndex
{
private:
const std::vector<int*> &index;
public:
ConstIndex( const std::vector<int*> &index ) : index(index) {}
public:
//Implement methods/types needed to emulate "const std::vector<const int*>"
const int *operator[]( size_t i ) const { return index[i]; }
const int *at( size_t i ) const { return index.at(i); }
...
};
Here is an ugly solution that works with versions before C++20 using reinterpret_cast:
const std::vector<const int*>& getIndex() const{
return reinterpret_cast<const std::vector<const int*>&>(data);
}
Note this does actually return a reference bound to an lvalue, not a const& bound to an rvalue:
std::vector<const int*>& getIndex() const{
return reinterpret_cast<std::vector<const int*>&>(data);
}
I have a simple question. I have a class say A with a private member that is a stl container, for example a vector of ints. Is there a way to use its methods (e.g. size, resize, etc...) ? Does the classic "get function" suffice ?
Class A
{
private:
std::vector<int> v;
public:
std::vector<int> get_v() {return v;};
};
If yes, isn't a "get function" meant to only get the member and not to modify the member ?
Thank you very much
The normal thing to do here is to return a constant reference to the data member:
const std::vector<int>& get_v() const
{
return v;
}
Note that I've also made the function constant. This tells you that the function will not modify any data members in the class.
Currently, you are taking a deep copy of the vector, which is expensive in terms of performance and also detaches you from the original data.
If you want to call methods like resize (that change the vector) then you can also supply a non-constant version of the function (overloading on const) is allowed in C++):
std::vector<int>& get_v()
{
return v;
}
The compiler will call the const version if you have a const pointer (or reference) to an instance of A. Else if will call the non-const version.
That getter will return a copy of the internal vector. Any modifications you make will only affect the copy. That might be okay if you also provide a setter so that the modified copy can be passed back, so you could do something like this:
A a;
std::vector<int> v = a.get_v();
v.push_back(5);
a.set_v(v);
Alternatively, you can make the getter return a reference to the internal vector (so its return type would be std::vector<int>&, then you can modify it from outside.
Your get method returns a copy of the data member. So such a method does not allow to modify the original vector,
If you want to provide an access to elements of the vector you can either overload operator[] or write a get method with a parameter.
For example
Class A
{
private:
std::vector<int> v;
public:
int operator []( std::vector<int>::size_type n ) const { return v[n]; }
int & operator []( std::vector<int>::size_type n ) { return v[n]; }
};
or you can write get methods that do the same
Class A
{
private:
std::vector<int> v;
public:
int get( std::vector<int>::size_type n ) const { return v[n]; }
int & get( std::vector<int>::size_type n ) { return v[n]; }
};
As for the idea to use a copy of the vector or a reference to it that to get its size then it is not a good idea. It is better to provide a method that returns the size of the vector. In this case the user interface will not depend on the type of the container.
Consider the following class:
class SomeInfo
{
private:
std::vector<someObj *> _myVector;
public:
const std::vector<const someObj*>& getInfoVector() const
{
return _myVector;
}
};
When I try to compile with gcc 4.1.2, it gives me the following error:
error: invalid initialization of reference of type
‘const std::vector<const someObj*, std::allocator<const someObj*> >&’
from expression of type
‘const std::vector<someObj*, std::allocator<someObj*> >
If I remove the 'const' in front of 'someObj *', then it compiles, but I don't want to return vector with non-constant pointers, because I don't want the objects referenced by them to be changed from outside my SomeInfo class. What would you do in this situation?
What I would do in this situation is forget about returning a vector (or reference thereto). The caller can't make any changes, so doesn't need to see any particular container. Other answers explain why you can't refer to your vector as if it were a vector of const someObj*. For that matter, if in future you change your class to use a deque instead of a vector you can't return a reference to that as if it were a vector either. Since callers just need access to the elements, let's give them that:
const someObj *getInfoAt(size_t n) const {
return _myVector.at(n);
}
const_info_iterator getInfoBegin() const {
return _myVector.begin();
}
const_info_iterator getInfoEnd() const {
return _myVector.end();
}
size_t getInfoSize() const {
return _myVector.size();
}
// plus anything else they need.
const_info_iterator is a typedef to a class that's slightly annoying to write. It has to wrap a std::vector<someObj *>::const_iterator and const-ify the type of operator*. boost::transform_iterator might be useful, or it's not all that bad to write from scratch. Bidirectional iterators do require the most operator overloads, though.
Now, if the caller really wants a vector of const someObj*, then using my interface they can easily get a snapshot at any particular moment:
std::vector<const someObj*> mycopy(myinfo.getInfoBegin(), myinfo.getInfoEnd());
What they can't have is a vector that continually reflects changes made to some other vector of a different type somewhere else -- std::vector just doesn't do that.
you could write getInfoVector like this:
std::vector<const someObj*> getInfoVector() const
{
std::vector<const someObj*> obj;
std::copy( _myVector.begin(), _myVector.end(), std::back_inserter( obj ) );
return obj;
}
std::vector<T*> and std::vector<const T*> are not same type. How can you write this:
std::vector<T*> obj(100);
std::vector<const T*> & ref = obj; //error
This is that you're doing in your code, which is invalid. You can make a reference of one type, to refer to object of other type.
So try this:
std::vector<const someObj*> getInfoVector() const
{
return std::vector<const someObj*>(_myVector.begin(), _myVector.end());
}
Now that it's returning a temporary vector which contains the pointers from the original vector. Also, note that return type is not reference anymore.
What would you do in this situation?
as an alternative to other answers, here is a simple approach which requires no copy or allocation:
const someObj* SomeInfo::getSomeObjAt(const size_t& idx) const {
return this->_myVector.at(idx);
}
even better in many contexts, just have SomeInfo not expose its internals.
If I have a vector as a private member in my class, what's the best way to access it? For example, take the following simple class
class MCL{
private:
std::vector my_vec;
public:
// Include constructor here and other member functions
}
What's the best way to access my_vec? Specifically, I would like to use a getter function to access it.
return it by const reference, or just by reference if you want to allow changing.
const std::vector<T> & getVector() const
{
return vector;
}
usage:
const std::vector<T> &v = myClass.getVector();
Create a public function called
std:vector getMyVec() {return my_vec;}
Depending on the semantics of your class, you may want to implement operator[]:
T& operator[](int i) {
return my_vec[i];
}
This way you can user [] to access the contents of your vector:
MCL a;
a[0] = 3;
std::cout << a[0] << std::endl;
Note that this may be considered abuse of operator[] or bad practice, but it is up to the developer to judge if this construct fits in the class, depending on its semantics.
Also note that this solution does not provides a way to insert or delete elements from the vector, just access to the elements already there. You may want to add other methods to do these or to implement something like:
T& operator[](int i) {
if(my_vec.size() < i)
my_vec.resize(i+1);
return my_vec[i];
}
Again, it is up to the semantics of your class and your usage pattern of it. This may or may not be a good idea.