0

I have a class ReferenceManager(a class which handles book or journal citations), which contains the data members:

private:
    int arraySize;
    Reference **references;

This is the main constructor of "ReferenceManager":

ReferenceManager::ReferenceManager(int capacity):
arraySize(capacity)
{
    Reference* arrayOfreferencePointers[capacity];
    references = arrayOfreferencePointers;
}

Reference is another class that I've created.

So, from what I understand, now we have a pointer Reference **references which points to an array of pointers of Reference*. All of these pointers are pointing to NULL right now.

So far so good.

Now, consider this member function of the ReferenceManager class:

bool ReferenceManager::insert(Reference &reference){
    for (int i = 0; i < arraySize; i++) {
        if((*(references) + i) == NULL){
            (*(references) + i) = &reference; //ERROR: expression not assignable
            cout << "Object placed in position " << i << "." << endl;
            return true;
        }
    }
    cout << "Array full!" << endl;
    return false;
}

This function is suppose to take the first available pointer in the array and point it to the object passed by reference.

I don't understand why i'm getting an "expression not assignable" error. By dereferencing "references" only once, shouldn't I be able to make the pointer point wherever I want?

LogicStuff
  • 19,397
  • 6
  • 54
  • 74
Georan
  • 117
  • 7
  • Trying to assign to `*(references) + i` is like trying to assign to `i + 5`. What are you trying to do with that assignment? – user2357112 Feb 15 '16 at 23:43
  • 4
    In your constructor you assign the address of a local array of pointers to your member variable `references` which gets destroyed as soon as the constructor ends. – Galik Feb 15 '16 at 23:54
  • @Galik BLESS YOU!! I think this is my real problem. – Georan Feb 16 '16 at 00:00
  • 1
    Both were real, only one was compile-time and the other runtime. – LogicStuff Feb 16 '16 at 00:02
  • @Galik How can I fix this? – Georan Feb 16 '16 at 00:02
  • 3
    Dynamic allocation or `std::vector`. – LogicStuff Feb 16 '16 at 00:02
  • @LogicStuff Like this? : references = new Reference*[capacity]; – Georan Feb 16 '16 at 00:04
  • 1
    `new Reference*[capacity]();` if you want it to contain `nullptr`s, which it didn't in the first case either. Don't forget the `delete []` and that what you pass to the `insert` function should have lifetime as long as you need it to have. This is a mess, though. – LogicStuff Feb 16 '16 at 00:06
  • @LogicStuff My whole program is working now! Those missing () were my real problem. Thanks a bunch! I wish I could offer you more than an upvote. – Georan Feb 16 '16 at 00:15

3 Answers3

3

This is because (*(references) + i) yields an rvalue to which you cannot assign. It is equivalent to (references[0] + i).

You probably wanted *(references + i) or references[i] equivalent. Choose the later, please. They both return an lvalue reference - Reference *&.

Community
  • 1
  • 1
LogicStuff
  • 19,397
  • 6
  • 54
  • 74
2

This is a bad start:

ReferenceManager::ReferenceManager(int capacity):
arraySize(capacity)
{
    Reference* arrayOfreferencePointers[capacity];
    references = arrayOfreferencePointers;
}

You have declared arrayOfreferencePointers within a function. This is called an automatic variable , and it gets destroyed when the function exits. This will leave your class member references as a dangling pointer, that is, a pointer that does not point to a valid object.

Instead you need to use dynamic allocation for your array. The best way to do this is to use a class which manages the dynamic allocation. You could write your own, but there is one to do this in the standard library already, it is called std::vector. You would replace References **references; with std::vector<Reference *> references; .

All of these pointers are pointing to NULL right now.

You never initialized any of them, so they are all wild pointers. You needed to use an initializer to set them to null. However if you follow my vector suggestion then you don't need to do this; growing a vector causes the new items to be initialized properly. (defaulting to null pointer in the case of pointers).

The insert function could remain as you have it (with the bracketing fixed as the other answers show); however it would lead to tidier code to grow the vector as it is needed, instead of doing a null pointer search.

NB. If you only ever instantiate a ReferenceManager with a size known at compile-time then this opens up another approach: making capacity be a template parameter, and using a class member array instead of a vector.


Speaking more generally about the design now, this is not a good idea:

insert(Reference &reference)

The reason is that, by convention, a function that accepts something by reference should not store any references to that object; it should only access the object for the duration of the function. Otherwise you may generate dangling references again. For example consider this code:

void func()
{
    Reference bob;
    manager->insert(bob);
    // oops...
}

Now the manager will contain a dangling reference, because bob has been destroyed but bob's address lives on inside the manager. This leads to disaster.

To indicate that you may store the object it'd be better to have the insert function accept a pointer. This serves as self-documentation that the function may store the pointer. Of course, someone could still write:

Reference bob;
manager->insert(&bob);

but at least this is more likely to raise a red flag to someone reading your code who is familiar with the conventions I have described.

It would be expected that passing &bob may store the address; but passing bob would store a copy.


Recapping that; it's important to think about the lifetime of objects that your ReferenceManager is referring to. You've basically got two options:

  • The manager also manages the lifetimes
  • The lifetimes are managed elsewhere and you make sure never to destroy an object while the manager holds a reference to the object.

The first option would be done by having your container store smart pointers.

M.M
  • 138,810
  • 21
  • 208
  • 365
1

References require a memory location or an lvalue. While *(references) is an lvalue, when you add it to i, it becomes an rvalue with no place in memory. This is something that you cannot have a reference to. :)

Untitled123
  • 1,317
  • 7
  • 20