I'm just trying to get C++ down and to do that, I gotta make my own libraries and whatnot. So I'm trying to get the beginnings of my own List template class, exactly like the List class in the JDK. So I've got the template and stuff down, I just wanna know how I would make it so that I could loop through the contents of a list object. This way I can print out the contents of said object. I don't exactly know where to start.
#pragma once
template<typename T> class List {
public:
List() : _size(0), _elements(nullptr) {}
~List() {
if (_elements != nullptr) {
delete[] _elements;
}
}
inline int size() { return _size; }
inline void add(T element) {
_size++;
T* buffer = new T[_size];
if (_elements != nullptr) {
memcpy(buffer, _elements, sizeof(T) * (_size - 1));
delete[] _elements;
}
buffer[_size - 1] = element;
_elements = buffer;
}
inline void remove(T element) {
_size--;
T* buffer = new T[_size];
if (_elements != nullptr) {
memcpy(buffer, _elements, sizeof(T) * _size);
delete[] _elements;
}
else {
assert(false);
}
_elements = buffer;
}
inline T* getElements() {
return _elements;
}
private:
int _size;
T* _elements;
};
And this is my main Cpp file
#include <cstdio>
#include <string>
#include "List.h"
using namespace std;
int main( int argc, char ** argv )
{
string str = "This is a test!";
List<char> list = breakString(str);
for (char c : list.getElements()) {
}
getchar();
return 0;
}
List<char> breakString(string str) {
List<char> list;
for (char c : str) {
list.add(c);
}
return list;
}
So what I'm trying to figure out is in that for loop, I'm used to it being an enhanced for loop like in Java, and is that the same here in C++? If so, how would I make this object iterable?
I figured I would get a more accurate answer to my question if I asked directly.
The guidelines for validating the Range-based for can be found on http://en.cppreference.com/w/cpp/language/range-for
If range_expression is an expression of a class type C that has a member named begin and/or a member named end (regardless of the type or accessibility of such member), then begin_expr is __range.begin() and end_expr is __range.end();
In short, since your data is in a raw array, you should add these public methods to you class List<T>
T* begin() { return _elements;}
T* end() { return _elements + _size;}
And then you can invoke the Range-based for on the object directly:
for (char c : list)
But you have other issues in your code. One of them (not sure it's the only one) is that breakString returns an object, but your class is not trivial and it does not define any copy or move constructors.
This answer adresses the "main" question as to how the enable the Range-based for for your class. It just needs two methods begin() and end() that return something that looks like an iterator (could be an actual iterator or a pointer to a contiguous memory chunk")
You can test the suggested modifications without using breakString, like this:
int main( int argc, char ** argv )
{
std::string str = "This is a test!";
List<char> list;
for (char c : "abcdef") { list.add(c); }
for (char c : list) { std::cout << c << "\n"; }
}
And to make it "fully iterable" define you can define a nested class called ierator..
Related
Im trying to create an iterator for my dynamic vector class that i've implemented,
The thing is I keep getting this error when I try to initilaize some student class for testing
purposes on some assignment for the iterator, I cannot figure out why Im having this error wasted alot of hours on it and its impossible to figure out why this error occurs..
Here is the code
some edit
Here is the main function that im trying to use that takes the iterator from my class
some edit
Just in the intiliaze of the DYNVECTOR class in the main my code fails , I keep on getting
the error:
error: cannot convert 'Student*' to 'DYNVECTOR <Student, 24>::Node*' in initialization
Iter(T *N ) : _pointer(N) { }
EDIT: please guys focus on this part:
inputIterator begin() { return inputIterator(pa);}
this is what is causing the error, the functionality of the push back function and other funtions are still in progress but that is not the reason causing this error.
The Problem
inputIterator begin() { return inputIterator(pa);}
is calling inputIterator's constructor
Iter(T *N ) : _pointer(N) { }
with a pointer to a T, a T *. Iter takes T * happily, but it tries to store that T * into _pointer and _pointer is defined as
Node *_pointer;
which is NOT a T *. The assignment fails because the types don't match.
The Naive Solution
Make the types match. This means you have to pass in a Node *. Bad news: DYNARRAY doesn't have any Node *s to give it. Naive solution fails.
The Proper Solution
Throw out Node. Node is useful if you have a linked list. You don't have a linked list. Kill it. Make it dead. Clean up the mess.
class DYNVECTOR
{
// no nodes
class Iter // directly uses T pointers
{
public:
Iter(T *N) :
_pointer(N) // types match now
{
}
T& operator*() const
{
return *_pointer; // simpler without Node, no?
}
T* operator->() const
{
return _pointer; // simple
}
Iter& operator++()
{
_pointer++; // dead simple
return *this;
}
Iter operator++(int)
{
Iter tmp = *this;
_pointer++; // yawn-city
return tmp;
}
bool operator==(Iter const &rhs) const
{
return _pointer == rhs._pointer; // unchanged
}
bool operator!=(Iter const &rhs) const
{
return _pointer != rhs._pointer; // unchanged
}
private:
T *_pointer; // T *, not Node *
};
private:
size_t someCap, length; //, initCap; don't see the point of initCap
T *pa; // unchanged
public:
typedef Iter inputIterator;
DYNVECTOR():
someCap(Capacity), // Still not sure what Capacity is for, so I used
// it here instead of magic number 24
length(0),
pa(new T[someCap])
{
// used initializer list instead.
}
inputIterator begin()
{
return inputIterator(pa); // unchanged
}
inputIterator end()
{
return inputIterator(&pa[length]); // iterator to one past the end.
// just like std::vector
}
template<class Iter>
DYNVECTOR(const Iter &begin, const Iter &end): // far more versatile if const references
DYNVECTOR() // delegate basic set-up to default constructor
{
for (Iter pointer = begin; pointer != end; pointer++) // loop unchanged
{
push_back(*pointer);
}
}
// make uncopyable (for now anyway) See Rule of Three
// linked below for why
DYNVECTOR(const DYNVECTOR & ) = delete;
DYNVECTOR& operator=(const DYNVECTOR & ) = delete;
~DYNVECTOR() // for my own testing. left as example
{
delete[] pa; // clean up allocated storage
}
void push_back(const T & newb) // for my own testing. left as example
{
if (length == someCap) // need more space
{
int newCap = someCap * 2; // double the size
// you might need to do something different like
// int newCap = someCap + Capacity;
// There's no way for me to know.
// The rest should be right though.
T* newArr = new T[newCap]; // allocate bigger array
for (size_t index = 0; index < length; index++)
{ // copy old array into new
newArr[index] = pa[index];
}
delete[] pa; // discard old array
pa = newArr; // use new array
someCap = newCap; // update capacity
}
pa[length] = newb; // insert new item
length++; // update counter
}
};
Documentation on the Rule of Three and friends. You cannot write complex and efficient C++ unless you understand these rules. Learn them or consign yourself to being a hack.
First of all when you deal with objects it is bad practice to do:
DYNVECTOR<Student, 24> students;
It should be:
DYNVECTOR<Student*, 24> students;
Secondly you never created a constructor for your DYNVECTOR how do you expect the object to be created??
I come from C/C# language and now I'm trying to learn about C++ and his standards functions.
Now, I'm creating a class called IMonsterDead. I will have a std::vector<IMonsterDead*> with N monsters.
Example:
class IMonsterDead {
public:
IMonsterDead(int Id)
{
this->_Id = Id;
}
virtual void OnDead() = 0;
int Id() const {
return _Id;
}
private:
int _Id;
};
One class which implements that class:
class MonsterTest : public IMonsterDead {
public:
MonsterTest(int generId)
: IMonsterDead(generId)
{
}
virtual void OnDead()
{
std::cout << "MonsterTesd died" << std::endl;
}
};
Ok, if I access directly everything works fine. But I'm trying to use std::find.
Full program test:
int main()
{
std::vector<IMonsterDead*> monsters;
for (int i = 0; i < 1000; i++)
{
monsters.emplace_back(new MonsterTest(1000 + i));
}
int id = 1033;
std::vector<IMonsterDead*>::iterator result = std::find(monsters.begin(), monsters.end(), [id]( IMonsterDead const* l) {
return l->Id() == id;
});
if (result == monsters.end())
std::cout << "Not found" << std::endl;
else
{
// Here I want to access OnDead function from result
}
return 0;
}
So I need to access OnDead function from result but I can't. Intellisense doesn't show anything for me. The result exists.
How can I access that function? Have another better way to do that?
You need to use std::find_if() instead of std::find(). std::find() is for finding an element with a specific value, so you have to pass it the actual value to find, not a user_defined predicate. std::find_if() is for finding an element based on a predicate.
Either way, if a match is found, dereferencing the returned iterator will give you a IMonsterDead* pointer (more accurately, it will give you a IMonsterDead*& reference-to-pointer). You need to then dereference that pointer in order to access any members, like OnDead().
You are also leaking memory. You are not delete'ing the objects you new. And when dealing with polymorphic types that get deleted via a pointer to a base class, the base class needs a virtual destructor to ensure all derived destructors get called properly.
With that said, you are clearly using C++11 or later (by the fact that you are using vector::emplace_back()), so you should use C++11 features to help you manage your code better:
You should use std::unique_ptr to wrap your monster objects so you don't need to delete them manually.
You should always use the override keyword when overriding a virtual method, to ensure you override it properly. The compiler can catch more syntax errors when using override than without it.
You should use auto whenever you declare a variable that the compiler can deduce its type for you. Especially useful when dealing with templated code.
Try something more like this:
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
class IMonsterDead {
public:
IMonsterDead(int Id)
: m_Id(Id)
{
}
virtual ~IMonsterDead() {}
virtual void OnDead() = 0;
int Id() const {
return m_Id;
}
private:
int m_Id;
};
class MonsterTest : public IMonsterDead {
public:
MonsterTest(int generId)
: IMonsterDead(generId)
{
}
void OnDead() override
{
std::cout << "MonsterTest died" << std::endl;
}
};
int main()
{
std::vector<std::unique_ptr<IMonsterDead>> monsters;
for (int i = 0; i < 1000; i++)
{
// using emplace_back() with a raw pointer risks leaking memory
// if the emplacement fails, so push a fully-constructed
// std::unique_ptr instead, to maintain ownership at all times...
monsters.push_back(std::unique_ptr<IMonsterDead>(new MonsterTest(1000 + i)));
// or:
// std::unique_ptr<IMonsterDead> monster(new MonsterTest(1000 + i));
// monsters.push_back(std::move(monster));
// or, if you are using C++14 or later:
// monsters.push_back(std::make_unique<MonsterTest>(1000 + i));
}
int id = 1033;
auto result = std::find_if(monsters.begin(), monsters.end(),
[id](decltype(monsters)::value_type &l) // or: (decltype(*monsters.begin()) l)
{
return (l->Id() == id);
}
// or, if you are using C++14 or later:
// [id](auto &l) { return (l->Id() == id); }
);
if (result == monsters.end())
std::cout << "Not found" << std::endl;
else
{
auto &monster = *result; // monster is 'std::unique_ptr<IMonsterDead>&'
monster->OnDead();
}
return 0;
}
Iterators are an interesting abstraction, in this case to be reduced to pointers.
Either you receive the pointer to the element or you get an invalid end.
You can use it as a pointer: (*result)->func();
You can also use it to create a new variable:
IMonsterDead &m = **result;
m.func();
This should give the same assembly, both possible.
So i have created a class that creates an array of objects from another class and when i try to execute the program and insert new objects to the array i get this error: EXC_BAD_ACCESS(code=EXC_I386_GPFLT)
Specifically on the insertion of the 10th element.
this is the class that has the array:
class Savelist{
private:
list_reserve *reserve;
list_user *users;
int length,MaxSize;
string code;
public:
string stoixeia;
Savelist(int MaxListSize)
{// Constructor for formula-based linear list.
MaxSize = MaxListSize;
reserve = new list_reserve (MaxSize, code);
users=new list_user(MaxSize);
length = 0;
}
~Savelist() {delete [] reserve,delete [] users;} // destructor
bool IsEmpty() const {return length == 0;}
int Length() const {return length;}
the method that inserts class objects to array:
void Insert_flight(list_reserve input)
{// "push" all objects one position up
if (length!=0)
{
for (int i=length-1; i >=0; i--)
{
reserve[i]=reserve[i+1];
}
reserve[0] = input;
length++;
}
else
{
reserve[0] = input;
length++;
}
}
here is the class that creates the objects that are being inserted to array:
class list_reserve {
private:
bool result;
int length,MaxSize;
string *object; // dynamic 1D array
public:
string code;
bool get_result;
// constructor
list_reserve(int MaxListSize,string flcode)
{// Constructor for formula-based linear list.
MaxSize = MaxListSize;
object = new string [MaxSize];
length = 0;
code=flcode;
}
~list_reserve() {delete [] object;} // destructor
bool IsEmpty() const {return length == 0;}
int Length() const {return length;}
the method that inserts simple strings to the object of that class:
void Insert_User(string input,string code,list_flight schedule)
{// Insert user
if (length!=0)
{
for (int i=length-1; i >=0; i--)
{
object[i+1] = object[i];
}
object[0] = input;
schedule.Get(code);
schedule.update(schedule.code, 1);
length++;
}
else
{
object[0] = input;
schedule.Get(code);
schedule.update(schedule.code, 1);
length++;
}
}
};
And lastly the main():
int main(int argc, const char * argv[]) {
list_reserve DA001_r(100,"DA001"),DA002_r(100,"DA002"),DA003_r(100,"DA003"),DA004_r(100,"DA004"),DA005_r(100,"DA005")
,DA006_r(100,"DA006"),DA007_r(100,"DA007"),DA008_r(100,"DA008"),DA009_r(100,"DA009"),DA010_r(100,"DA010"),DA011_r(100,"DA011")
,DA012_r(400,"DA012"),DA013_r(400,"DA013"),DA014_r(100,"DA014"),DA015_r(100,"DA015"),DA016_r(100,"DA016"),DA017_r(100,"DA017")
,DA018_r(100,"DA018"),DA019_r(100,"DA029"),DA020_r(100,"DA020"),DA021_r(100,"DA021"),DA022_r(100,"DA022"),DA023_r(400,"DA023"),
DA024_r(400,"DA024");
Savelist Queues(100);
Queues.Insert_flight(DA001_r);
Queues.Insert_flight(DA002_r);
Queues.Insert_flight(DA003_r);
Queues.Insert_flight(DA004_r);
Queues.Insert_flight(DA005_r);
Queues.Insert_flight(DA006_r);
Queues.Insert_flight(DA007_r);
Queues.Insert_flight(DA008_r);
Queues.Insert_flight(DA009_r);
Queues.Insert_flight(DA010_r);
Queues.Insert_flight(DA011_r);
Queues.Insert_flight(DA012_r);
Queues.Insert_flight(DA013_r);
Queues.Insert_flight(DA014_r);
Queues.Insert_flight(DA015_r);
Queues.Insert_flight(DA016_r);
Queues.Insert_flight(DA017_r);
Queues.Insert_flight(DA018_r);
Queues.Insert_flight(DA019_r);
Queues.Insert_flight(DA020_r);
Queues.Insert_flight(DA021_r);
Queues.Insert_flight(DA022_r);
Queues.Insert_flight(DA023_r);
Queues.Insert_flight(DA024_r);
}
You really, really don't want to store owning pointers to dynamically allocated resources in classes. For a good rundown on why see this answer on what's necessary to store an owning pointer in a class. Using a standard container such as std::vector also greatly simplifies the code as these implement most common operations you may want to do with them, such as insertion, deletion and the like.
For instance, your Savelist class would become the following when using std::vector
class Savelist {
private:
vector<list_reserve> reserve;
vector<list_user> users;
// No need for storing MaxSize or length, std::vector can store any
// number of elements, and knows how many elements it contains.
string code;
public:
string stoixeia;
// constructor and destructor becomes unnecessary, as the vector member
// takes care of all the bookkeeping
bool IsEmpty() const { return reserve.empty(); }
int Length() const { return reserve.size(); }
// Insert flight need only pass the input to the vector
void Insert_flight(list_reserve input) { reserve.insert(reserve.begin(), input); }
};
And similarly for the list_reserve class
class list_reserve {
private:
bool result;
// once again, the vector keeps track of size and elements contained
vector<string> object;
public:
string code;
bool get_result;
// constructor becomes simpler
list_reserve(string flcode)
{
code = flcode;
}
// destructor becomes unnecessary
bool IsEmpty() const { return object.empty(); }
int Length() const { return object.size(); }
void Insert_User(string input, string code, list_flight schedule)
{
// note that object.push_back(input); would be
// more efficient, but inserts the element at the end.
object.insert(object.begin(), input);
schedule.Get(code);
schedule.update(schedule.code, 1);
}
};
See also cppreference page on std::vector for an excellent reference on what's available.
New to c++ and OOP. I'm trying to figure out lists and iteration, so I've created the following example code. I create a couple Thing objects, but I want to make sure that when a Thing is created, its constructor adds it to a list "things" (inside the lists object) so that I can keep track of every instance of Thing. At the bottom of main() I then iterate through the list of Things. Is there a better way to do this, or could you point out how to do this in my Thing constructor? Thanks!!
#include <iostream>
#include <list>
class Thing;
class Lists
{
public:
std::list<Thing> things;
Lists() {
std::cout << "List object with list 'things' created" << std::endl;
}
};
class Thing
{
public:
int howMuch, pointer;
Thing(int x, Lists* y)
{
howMuch = x;
y->things.push_back(this);
}
};
int main()
{
//create the object that holds the list of things
Lists lists;
//make some objects, and pass a pointer of the lists to the constructor
Thing thingA(123, &lists);
Thing thingB(456, &lists);
for (std::list<Thing>::iterator it = lists.things.begin(); it != lists.things.end(); ++it)
std::cout << "test" << it->howMuch << std::endl;
return 0;
}
You can store created items inside the Thing class itself using a static field _things:
#include <iostream>
#include <list>
class Thing
{
static std::list<Thing> _things;
public:
int howMuch, pointer;
Thing(int x) : howMuch(x)
{
_things.push_back(*this);
}
static std::list<Thing> getAllThings()
{
return _things;
}
};
std::list<Thing> Thing::_things;
int main()
{
Thing thingA(123);
Thing thingB(456);
auto allThings = Thing::getAllThings();
for (auto it = allThings.begin(); it != allThings.end(); ++it)
std::cout << "test " << it->howMuch << std::endl;
return 0;
}
The original example and the example in answer 1 encounter problems as soon as any Thing is destroyed (as François Andrieux mentioned), even if you use a pointer to Thing in the list. If you use a Thing in a subroutine as a local variable, the Thing is destroyed at the end of this function, but is still in the list. To solve this problem, you have to remove the Thing from the list in the destructor of Thing. But if you do so, you get a problem, when Thing is a global object. You have two global objects - the list and the Thing. It is not clear, which is destroyed first, so you can end up whith an access violation, which is difficult to debug, because it happens after exit().
Here is my proposal:
template<class T>
class InstanceIterator{ // Iterator for an InstanceList
public:
InstanceIterator(T*pT)
: pt(pT)
{}
T& operator*(){ return *pt; }
T* operator->(){ return pt; }
InstanceIterator operator++(){
pt=pt->instanceList.pNext;
return *this;
}
int operator!=(const InstanceIterator<T>& i){ return i.pt!=pt; }
private:
T*pt;
};
template<class T>
class InstanceList{
// this class means not the whole list, but only the element (pNext)
// which is inserted into the object you want to have in a list.
// there is no explizite list, every instance class T has a part of the list
public:
InstanceList(){};
void insert(T* pt){ // gets the this-pointer of the surrounding class
pNext=pFirst;
pFirst=pt;
}
~InstanceList();
static InstanceIterator<T> begin(){ return pFirst; }
static InstanceIterator<T> end(){ return 0; }
static bool empty(){ return pFirst==0; }
private:
InstanceList(const InstanceList&);// no copy constructor
void operator=(const InstanceList&);// no assignment
static T* pFirst;
T* pNext;
friend class InstanceIterator<T>;
};
template<class T>
InstanceList<T>::~InstanceList(){
T**ppInst=&pFirst;
// search for myself
while(&((*ppInst)->instanceList)!=this) { // its me?
if(0==(*ppInst)) {
return; // emergency exit
}
ppInst=&((*ppInst)->instanceList.pNext); // the next please
}
// and remove me from the list
(*ppInst)=pNext;
}
template<class T>
T* InstanceList<T>::pFirst=0;
// how to use and test the above template:
// (uses 3 objects: one is global, one is local,
// and one is deleted before going through the list)
class InstanceTest { // example class, the instances of this class are listed
public:
InstanceTest(int i)
: i(i)
{
instanceList.insert(this); // dont forget this line
}
InstanceList<InstanceTest> instanceList; // must have this line with exact this name
int i;
};
InstanceTest t1(1); // a global object
int main() {
std::cout << "testing InstanceIterator";
InstanceTest t2(2); // a local object
InstanceTest* pt3 = new InstanceTest(3); // will be deleted later
int sum(0);
for(InstanceIterator<InstanceTest> it= InstanceList<InstanceTest>::begin(); it!= InstanceList<InstanceTest>::end();++it){
sum += it->i;
}
int testFailed(0);
if (sum != 6) testFailed++;
delete pt3;
sum = 0;
for (InstanceIterator<InstanceTest> it = InstanceList<InstanceTest>::begin(); it != InstanceList<InstanceTest>::end(); ++it) {
sum += it->i;
}
if (sum != 3) testFailed++;
if (testFailed) {
std::cout << "... FAILED !!!\n";
}
else std::cout << "... OK\n";
return testFailed;
}
I have a class idx_aware that goes into a container container, which wraps around a std::vector. When the class is added to container, container sets a pointer to itself in idx_aware, as well as the index of idx_aware in its internal memory storage.
The index is not going to change until the container is destroyed or idx_aware is removed; idx_aware needs to know about its container and its index, because it has some methods that require both to work.
Now this introduces the following problem: when I get a non-const reference to an idx_aware class contained in container, I could assign to it another idx_aware class, which could have a different index. The intention would be assigning all the fields and keeping the index as it is.
#include <vector>
#include <limits>
#include <iostream>
class container;
// Stores a std::size_t field, which can be set only by subclasses.
class with_idx {
std::size_t _i;
public:
with_idx() : _i(std::numeric_limits<std::size_t>::max()) {}
operator std::size_t() const { return _i; }
protected:
void set_idx(std::size_t i) { _i = i; }
};
// Knows its index and its container
class idx_aware : public with_idx {
container const *_container;
int _some_field1;
float _some_field2;
public:
void foo() {
// Do stuff using _container and _i
}
private:
friend class container;
};
// Wraps around a std::vector
class container {
std::vector<idx_aware> _data;
public:
idx_aware &operator[](std::size_t idx) {
// Need non-const access to call foo
return _data[idx];
}
idx_aware const &operator[](std::size_t idx) const {
return _data[idx];
}
std::size_t add(idx_aware const &item) {
// Here it could potentially reuse a freed position
std::size_t free_slot = _data.size();
// Ensure _data is big enough to contain free_slot
if (_data.size() <= free_slot) {
_data.resize(free_slot + 1);
}
// Assign
_data[free_slot] = item;
_data[free_slot].set_idx(free_slot);
_data[free_slot]._container = this;
return free_slot;
}
};
int main() {
container c;
idx_aware an_item;
std::size_t i = c.add(an_item);
std::cout << c[i] << std::endl; // Prints 0
idx_aware another_item; // Created from somewhere else
// I want to set all the data in idx_aware, but the
// index should stay the same!
c[i] = another_item;
std::cout << c[i] << std::endl; // Prints numeric_limits<size_t>::max()
// Now container[i] is broken because it doesn't know anymore its index.
return 0;
}
One possible workaround would be to change with_idx in such a way that when set_idx is called, a flag is set that prevents assignment and copy operator to overwrite the _i property, like this:
class with_idx {
std::size_t _i;
bool _readonly;
public:
with_idx() : _i(std::numeric_limits<std::size_t>::max()), _readonly(false) {}
with_idx(with_idx const &other) : _i(other._i), _readonly(false) {}
with_idx &operator=(with_idx const &other) {
if (!_readonly) {
_i = other._i;
}
return *this;
}
operator std::size_t() const { return _i; }
protected:
void set_idx(std::size_t i) {
_i = i;
if (i != std::numeric_limits<std::size_t>::max()) {
// This has been set by someone with the right to do so,
// prevent overwriting
_readonly = true;
} else {
// Removed from the container, allow overwriting
_readonly = false;
}
}
};
This would have the consequence of returning, after assignment, a reference to an idx_aware class with unchanged index.
idx_aware ¬_in_container1 = /* ... */;
idx_aware ¬_in_container2 = /* ... */;
idx_aware &in_container = /* ... */;
not_in_container1 = in_container = not_in_container2;
// std::size_t(not_in_container_1) != std::size_t(not_in_container_2)
Is there a design pattern that can model this situation in a better way? My searches were not successful.
Are there other unwanted consequences of overriding the assignment operator in this way? The limitation I pointed out in the previous example does not look too "bad".
Is there an easier solution? I thought about writing some proxy object to replace the idx_aware & return type of operator[].
Experience tells that when C++ does not do what you intend, you are likely to be misusing OOP...
Robert's comment suggested me this solution.
Why would the contained object know about its container? To be able to perform actions such as foo and provide shorthand methods that otherwise would require to have access to the container.
Let's take this functionality away from the contained object; the contained object is just data payload. Instead, let's make operator[] return not the contained object, but some sort of iterator, a wrapper around the contained object, which knows the container and the index, and once dereferenced returns the actual contained object.
class was_idx_aware {
int _some_field1;
float _some_field2;
};
class container {
std::vector<idx_aware> _data;
public:
class idx_aware_wrapper {
container const *_container;
std::size_t _idx;
public:
idx_aware_wrapper(container const &c, std::size_t i)
: _container(&c)
, _idx(i)
{}
was_idx_aware const &operator*() const {
return _container->_data[_idx];
}
was_idx_aware &operator*() {
return _container->_data[_idx];
}
void foo() {
// Do stuff using _container and _idx.
}
};
idx_aware_wrapper operator[](std::size_t i) {
return idx_aware_wrapper(*this, i);
}
/* .... */
};
This allows quick access to any data in was_idx_aware, and the wrapper class can be augmented with all the methods that require interaction with the container. No need to store and keep indices up to date or override assignment operators.