-3

I'm writing a library application. Part of the application is allowing users to login. I'm using a vector of structs to store the usernames/passwords. When I try to access a member variable of the struct, I get an out of range error. The vector is full (I checked with both the vector.size and vector.empty methods), and I believe I am assigning values to the member variables correctly (although clearly I'm not).

Here is main.cpp:

#include <iostream>
#include <string>
#include <vector>
#include <fstream>

#include "userService.h"

using namespace std;

void countRecords(string& pathName, ifstream& inFile);
void loadCredentials(string pathName, ifstream& inFile, string& username, string& password, userService newUser);
void login(string username, string password, userService newUser, bool& loggedIn);

int numRecords;

int main()
{
    string username, password;
    string pathName = "/home/seth/Desktop/credentials";
    ifstream inFile;
    userService newUser;
    char menuSelection;
    bool loggedIn = false;

    countRecords(pathName, inFile);

    cout << "Welcome to Library Information System." << endl << endl;

    do{
        cout << "choose a) to login or b) to register as a new user." << endl << endl;
        cin >> menuSelection;
        switch (menuSelection)
        {
        case 'a':
        {
            cout << "Username: ";
            cin >> username;
            cout << endl;
            cout << "Password: ";
            cin >> password;
            cout << endl;
            loadCredentials(pathName, inFile, username, password, newUser);
            login(username, password, newUser, loggedIn);
            if (loggedIn == true)
            {
                cout << "You logged in! " << endl; //placeholder, will be more menu options here
            }
            else
            {
                cout << "Invalid credentials! Please check your username and password and try again!" << endl;
            }
            break;
        }
        }
    } while (loggedIn == false);


    return 0;
}
void countRecords(string& pathName, ifstream& inFile)
{
    string temp; //string to count records using getline

    inFile.open(pathName);

    while (inFile)
    {
        getline(inFile, temp, '\n');
        if (inFile.eof())
        {
            break;
        }
        ++numRecords;
    }
    cout << "numRecords = " << numRecords << endl;
    inFile.close();
    inFile.clear(std::ios_base::goodbit);
}

void loadCredentials(string pathName, ifstream& inFile, string& username, string& password, userService newUser)
{
    string tempUsername, tempPassword;

    inFile.open(pathName);

    if (!inFile)
    {
        cout << "Error opening file" << endl;
    }

    for (int i = 0; i < numRecords; i++)
    {
        getline(inFile, tempUsername, ',');
        getline(inFile, tempPassword, '\n');
        newUser.loadCredentials(tempUsername, tempPassword);
    }

}

void login(string username, string password, userService newUser, bool& loggedIn)
{

    newUser.resetVectorCounter();

    for (size_t i = 0; i < numRecords; i++)
    {
        cout << "i = " << i << endl;
        cout << newUser.getUsername() << endl;
        cout << newUser.getPassword() << endl;
        newUser.incrementVector();
    }   
}

userService.h:

#include <string>
#include <fstream>
#include <vector>

using namespace std;

struct credentials
    {
        string username = "";
        string password = "";

    };

class userService
{

private:

    vector<credentials> credentialsList;
    int vectorCounter = 0;
    string username_, password_;

public:

    void loadCredentials(string username_, string password_);
    bool check();
    int sizeOfVec();
    string getUsername();
    string getPassword();
    void incrementVector();
    void resetVectorCounter();
};

Implementation of userService:

#include <iostream>
#include <string>
#include <fstream>
#include <vector>

#include "userService.h"

using namespace std;

credentials users;

void userService::loadCredentials(string username_, string password_)
{
    users.username = username_;
    users.password = password_;
    credentialsList.push_back(users);
}

bool userService::check()
{
    return credentialsList.empty();
}
int userService::sizeOfVec()
{
    return credentialsList.size();
}
string userService::getUsername()
{
    return credentialsList.at(vectorCounter).username;
}
string userService::getPassword()
{
    return credentialsList.at(vectorCounter).password;
}
void userService::incrementVector()
{
    vectorCounter++;
}

void userService::resetVectorCounter()
{
    vectorCounter = 0;
}

The exact error that is being thrown is:

'std::out_of_range' what(): vector::_M_range_check: __n (which is 0) >= this->size (which is 0)

This happens immediately after calling getUserName. I believe this means the member variables are empty, but if so, I do not know how to assign them values properly. Any help would be appreciated.

I've tried using a debugger, and here is where the debugger shows this problem is:

protected:
  /// Safety check used only from at().
  void
  _M_range_check(size_type __n) const
  {
if (__n >= this->size())
  __throw_out_of_range_fmt(__N("vector::_M_range_check: __n "
                   "(which is %zu) >= this->size() "
                   "(which is %zu)"),
               __n, this->size());
  }
  • Your `vectorCounter`'s value is obviously wrong. I'm curious, how exactly do you expect anyone here to figure out why it ends up being wrong, based on all the information that's included in your question? You say that "vectorCounter is started at 0, and won't count above the number of usernames/passwords". Well if that's the case, you have a defective C++ compiler. That is extremely unlikely, so your base assumption must be wrong. Unfortunately, without a [mcve], the only thing you can hope for here, is for people to start making random guesses as to what your bug is. – Sam Varshavchik Dec 10 '17 at 01:21
  • Please provide the complete minimal example. In your post it is absolutely unclear how you access `loadCredentials` itself (not its elements). It is most probable that you use vector copies in your functions. It definitely goes from the error message that informed you about zero-sized vector. – 273K Dec 10 '17 at 01:22
  • `vectorCounter` -- You really should cut down or just eliminate extraneous user variables that denote the number of entries in a vector. The vector has a `size()` function that never gets the number of entries incorrect. By using unnecessary variables to keep track of sizes, you are risking doing something such as not updating the variable(s), which I would guess is one reason for your issue now. – PaulMcKenzie Dec 10 '17 at 01:28
  • Open your C++ book to the chapter that explains the difference between passing arguments to function by value versus by reference, and the fundamental difference between the two approaches. After you fully understand the underlying concepts, you should be able to look at this code, and immediately figure out your obvious bug. You could've also, probably, figured it out by using a debugger to step through your code, one line at a time, and examine the values of all variables at each step. Knowing how to use a debugger is a required skill for every C++ developer. – Sam Varshavchik Dec 10 '17 at 01:43
  • Passing an argument by value creates a copy, whereas passing by reference passes the address to the original variable. When you pass by reference you can modify the original variable. I guess you are seeing somewhere where I should have passed by reference instead of value. Can you point me in the right direction? – Seth Taylor Dec 10 '17 at 02:26
  • You seem to be repeatedly loading the credentials file. I recommend instead loading it once and keeping the `vector` around for the next time you need it. Save you a bunch of code and processing time. – user4581301 Dec 10 '17 at 02:44
  • `while (inFile) { getline(inFile, temp, '\n'); if (inFile.eof())` is almost the right idea, but it ignores that there are many things that can go wrong wrong with reading a file other in addition to it hitting the end of a file. This sets you up for an infinite loop if any of those many things happens and you never reach the end of the file. Give `while (getline(inFile, temp)) {` a shot. – user4581301 Dec 10 '17 at 02:48
  • I appreciate the feedback, and will clean that code up. However, that section of the program is working fine. It's when I try to return a member variable from the vector of structs that I run into a problem. Do you have any ideas how to solve that problem? – Seth Taylor Dec 10 '17 at 03:00

1 Answers1

-1

Your code does indeed accesses a std::vector using an out of bounds index.

Here's the stack trace from gdb on my machine:

#0  0x00007ffff74aa428 in __GI_raise (sig=sig@entry=6)
    at ../sysdeps/unix/sysv/linux/raise.c:54
#1  0x00007ffff74ac02a in __GI_abort () at abort.c:89
#2  0x00007ffff7ae484d in __gnu_cxx::__verbose_terminate_handler() ()
   from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3  0x00007ffff7ae26b6 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4  0x00007ffff7ae2701 in std::terminate() ()
   from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x00007ffff7ae2919 in __cxa_throw ()
   from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6  0x00007ffff7b0b3f7 in std::__throw_out_of_range_fmt(char const*, ...) ()
   from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#7  0x00000000004029b6 in std::vector<credentials, std::allocator<credentials> >::_M_range_check (this=0x7fffffffe320, __n=0)
    at /usr/include/c++/5/bits/stl_vector.h:803
#8  0x00000000004024cf in std::vector<credentials, std::allocator<credentials> >::at (this=0x7fffffffe320, __n=0) at /usr/include/c++/5/bits/stl_vector.h:824
#9  0x000000000040179d in userService::getUsername[abi:cxx11]() (
    this=0x7fffffffe320) at socc.cc:61
#10 0x0000000000402059 in login (username="a", password="aa", newUser=..., 
    loggedIn=@0x7fffffffe19f: false) at socc.cc:185
#11 0x0000000000401aa4 in main () at socc.cc:120

My "credentials" file has the following lines:

a,aa
b,bb
c,cc

Hence, when I use "a" for username and "aa" as password, the login should have succeeded.

However, you have:

string userService::getUsername()
{
   return credentialsList.at(vectorCounter).username;
}

when that function is called, credentialsList is empty and vectorCounter is 0. That is not OK.

That explains why you get the error. However, the fix is not simple.

I think there is lack of clarity on your part as far as how the data from the "credentials" file needs to be stored and how they can be used to authenticate users.

Here are some points to ponder.

  1. userService can provide the ability to authenticate users. However, it is not a user. Hence, it makes no sense at all why you have username_ and password_ as member variables of the class.

  2. The list of credentials from the "credentials" file is global data of your program. It makes no sense to store them in a member variable of userService unless your program can guarantee that there is only instance of userService.

  3. Having vectorCounter as a member variable of userService makes no sense at all either. When you need to iterate over the elements of the vector, there are better alternatives. Not only that, the logic for iteration should be local to the function, and any local variables thereof should be function local variables.

  4. Many of the member functions of userService are not appropriate for the responsibility of the class. The only member functions that make sense to me are:

    void loadCredentials(string filename);
    bool authenticateUser(credentials const& c);
    

Here's an updated program that works for me (except that you don't have any code for user choice 'b').

#include <iostream>
#include <string>
#include <fstream>
#include <vector>

using namespace std;

struct credentials
{
   string username;
   string password;
};

class userService
{
   private:

      vector<credentials> credentialsList;

   public:

      void loadCredentials(string filename);
      bool authenticateUser(credentials const& c);
};

void userService::loadCredentials(string filename)
{
   string tempUsername, tempPassword;

   std::ifstream inFile(filename);

   if (!inFile)
   {
      cout << "Error opening file" << endl;
   }

   while (inFile )
   {
      if ( !getline(inFile, tempUsername, ',') )
      {
         return;
      }

      if ( !getline(inFile, tempPassword, '\n') )

      {
         return;
      }

      credentialsList.push_back(credentials{tempUsername, tempPassword});
   }
}

bool userService::authenticateUser(credentials const& c)
{
   for ( auto const& item : credentialsList )
   {
      if ( item.username == c.username &&
           item.password == c.password )
      {
         return true;
      }
   }
   return false;
}

int main()
{
   string username, password;
   // string pathName = "/home/seth/Desktop/credentials";
   string pathName = "credentials";

   // The only instance of userService used in the program.
   userService service;

   char menuSelection;
   bool loggedIn = false;

   // Load the credentials file once and then use for all subsequent checks.
   service.loadCredentials(pathName);

   cout << "Welcome to Library Information System." << endl << endl;

   do {
      cout << "choose a) to login or b) to register as a new user." << endl << endl;
      cin >> menuSelection;
      switch (menuSelection)
      {
         case 'a':
            {
               cout << "Username: ";
               cin >> username;
               cout << endl;
               cout << "Password: ";
               cin >> password;
               credentials c = {username, password};

               // The only instance of userService has all the credentials.
               // It can authenticate the user credentials.
               loggedIn = service.authenticateUser(c);
               if (loggedIn)
               {
                  cout << "You logged in! " << endl; //placeholder, will be more menu options here
               }
               else
               {
                  cout << "Invalid credentials! Please check your username and password and try again!" << endl;
               }
            }
            break;

         // Missing code for case 'b'
         // case 'b':
      }
   } while (loggedIn == false);

   return 0;
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • I use the values 0-4. The for loop that calls the accessor functions goes from 0 - 4. – Seth Taylor Dec 10 '17 at 01:23
  • 1
    @SethTaylor, in that case, it will be better for you to post a [mcve]. Otherwise, we will end up speculating on where the problem could be. – R Sahu Dec 10 '17 at 01:24
  • @R Sahu, thanks a lot for that code. Thanks for giving me all of those things to ponder. I think I need to spend more time designing and thinking about the solution up front. – Seth Taylor Dec 11 '17 at 03:26
  • One thing I don't quite get: What exactly is happening in with 'for (auto const& item : credentialsList). How exactly does that piece of code work? – Seth Taylor Dec 11 '17 at 03:27
  • @SethTaylor, that function returns `true` it can authenticate a user with the given credentials. Otherwise, it returns `false`. I am not sure which part of the function does not make sense to you. – R Sahu Dec 11 '17 at 03:32
  • @R Sahu. Sorry, should have clarified. The part that doesn't make sense to me is: `for (auto const& item : credentialsList)`. It looks like `item` is a derived vector from `credentialsList`. Does `item` also inherit all of the values that are stored in `credentialsList`? I've been reading about the auto keyword, and it seems like, in this case, it is telling the compiler to create `item` with the same characteristics as `credentialsList`. I would just like to understand exactly what is happening there, and if there would be a different way to write that code (just for my own understanding) – Seth Taylor Dec 11 '17 at 03:38
  • If you are able to use C++11, it's time to learn about range-for loops. http://www.stroustrup.com/C++11FAQ.html#for and http://en.cppreference.com/w/cpp/language/range-for are good starting points. – R Sahu Dec 11 '17 at 03:40
  • I feel like I just had a "I've seen the light" moment! Thanks for sharing that and for getting my code working. Much appreciated! – Seth Taylor Dec 12 '17 at 02:15
  • @SethTaylor, glad I was able to help. – R Sahu Dec 12 '17 at 06:37