Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Daisy-chaining function calls on pointers

I encountered a scenario recently whilst working with a student, and I'm struggling to understand why the following example is failing.

I have a pointer to an object Game, and Game itself has a pointer to vector<Pair>. The failing line is the last line of of main(), where I am daisy-chaining methods:

gamePointer->getPairs()->push_back(pair);

In the above line, getPairs() returns a vector<Pair>*, and then push_back() is called to add a new Pair to the vector. This results in a read access violation. Interesting, swapping out the Game's vector<Pair> with a string, say, allows me to write the following, and it works:

gamePointer->getPairs()->append("B");

I've simplified the problem and reproduced a full example:

#include "pch.h"
#include <iostream>
#include <string>
#include <vector>

using namespace std;

class Pair 
{
private:
    string previous;
    string next;

public:
    Pair();
    Pair(string previous, string next);

    string getPrevious();
    string getNext();

    void setPrevious(string previous);
    void setNext(string next);
};

class Game 
{
private:
    vector<Pair>* pairs;
public:
    Game();

    vector<Pair>* getPairs();
    void setPairs(vector<Pair>* pairs);
};


Pair::Pair()
{
    this->setPrevious("a");
    this->setNext("b");
}

Pair::Pair(string previous, string next)
{
    this->setPrevious(previous);
    this->setNext(next);
}

string Pair::getPrevious()
{
    return this->previous;
}

string Pair::getNext()
{
    return this->next;
}

void Pair::setPrevious(string previous)
{
    this->previous = previous;
}

void Pair::setNext(string next)
{
    this->next = next;
}

Game::Game()
{
    vector<Pair> pairs;
    pairs.reserve(10);

    this->setPairs(&pairs);
}

vector<Pair>* Game::getPairs()
{
    return this->pairs;
}

void Game::setPairs(vector<Pair>* pairs)
{
    this->pairs = pairs;
}

int main()
{
    Game game;
    Game* gamePointer = &game;

    Pair pair("Previous", "Next");
    gamePointer->getPairs()->push_back(pair);
}
like image 822
Matt Griffiths Avatar asked Dec 13 '25 01:12

Matt Griffiths


1 Answers

Game::Game()
{
    vector<Pair> pairs; // DANGER!
    pairs.reserve(10);

    this->setPairs(&pairs); // ARGHH!
} // < pairs dies on this line

The vector named pairs only lives while the constructor is running. You store a pointer to this, but the pointed-to-object immediately goes out of scope!

Instead, just make the member a vector instead of a pointer:

class Game 
{
private:
    vector<Pair> pairs; // the vector itself is a member of Game

You could then make getPairs like this:

vector<Pair>* Game::getPairs() // return a pointer
{
    return &pairs;
}

or this:

vector<Pair>& Game::getPairs() // return a reference
{
    return pairs;
}

What you are doing currently is Undefined Behaviour - this means your program is illegal, and anything could happen, including appearing to work normally.

"Appearing to work normally" is what you are seeing when you swap the vector for a string - your code is still broken, you just don't notice!


I can make an educated guess about why that happens, but this is in no way guaranteed.

vector behaviour:

  • The vector object itself is on the stack, but it has to allocate a buffer on the heap using new.
  • It then deletes this buffer when the vector goes out of scope at the end of Game::Game().
  • The vector object itself is no longer valid, but the memory just happens to not get overwritten before you next try to use it.
  • You try to use the (no longer alive) vector, and the memory still happens to contain a pointer to the buffer. The buffer has been released, so you get a "read access violation" when trying to access it.

string behaviour:

  • The string does not have a to allocate a buffer. It is a valid implementation of std::string for it to use a "Small String Optimisation", where small strings (let's say, up to 16 characters, for example) are stored directly inside the string object itself, rather than in an allocated buffer.
  • Therefore the string, including the actual content, are on the stack.
  • The string object goes out of scope at the end of Game::Game(), but the memory just happens to not get overwritten before you next try to use it.
  • You try to use the (no longer alive) string, and the memory still happens to contain the valid "short string" magic.
  • Because this is on the stack, not the heap, the memory hasn't actually been released. So trying to access it does not cause a "read access violation".
  • It is still totally illegal though!
like image 183
BoBTFish Avatar answered Dec 14 '25 18:12

BoBTFish



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!