-4

I am learning about dynamic allocation of memory in C++. I've come across a problem for which I can't seem to find an answer.

In my program, I have a struct which goes like this:

struct Friend
{
    string name = "";
    int daysSinceContact = 0;
};

struct User
{
    string name = "";
    string password = "";
    int numberOfFriends = 0;
    Friend *friends = new Friend[numberOfFriends];
};

In my program, I create an array of users, which goes something like this:

int numberOfUsers = 5;
User *usersInformation = new User[numberOfUsers];

And it works fine. But I'd like to be able to add more friends to a chosen user, for example:

int nFriends = usersInformation[0].numberOfFriends; 
usersInformation[0].numberOfFriends++;
usersInformation[0].friends[nFriends-1].name = "John";
usersInformation[0].friends[nFriends-1].daysSinceContact = 2;

I'm guessing that I should use a buffer to copy the information from the array containing information about friends, and do something like this:

delete[] usersInformation[0].friends[];
usersInformation[0].numberOfFriends++;
usersInformation[0].friends = new Friend[numberOfFriends];

And then copy it back and add information about the new friend. But when I've tried, it didn't work.

Do you have any tips?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 1
    Change the `Friend*` to `std::vector` and have your problems all solved – Cory Kramer Aug 03 '17 at 19:18
  • That might be true as I haven't yet read about vectors. Maybe I'm just trying to do something that I can't yet do with the tools that I have. – Rafał Myśliwczyk Aug 03 '17 at 19:19
  • You can certainly do what you're describing with dynamic arrays, but it will take a lot of book-keeping on your end to make sure `new`ing, `delete`ing, and copying work correctly – Cory Kramer Aug 03 '17 at 19:20
  • The thing that I have the greatest trouble with is this part. Somehow my compiler is preventing me from doing delete[] usersInformation[0].friends[]; is this a correct way to delete this array inside an array that I already have? – Rafał Myśliwczyk Aug 03 '17 at 19:22
  • Oh, I think i should just do delete[] usersinformation[0].friends – Rafał Myśliwczyk Aug 03 '17 at 19:26
  • @RafałMyśliwczyk No, it isn't. Read a [good C++ book](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) on the basic syntax. `delete[] usersInformation[0].friends;` would be the correct syntax. – Algirdas Preidžius Aug 03 '17 at 19:28
  • Why have you read about new/delete, but not about std::vector? Your book isn't worth the paper it's printed on. –  Aug 03 '17 at 19:30
  • Thanks. I have just noticed that. But it seems i was just too blindfolded because of frustration. – Rafał Myśliwczyk Aug 03 '17 at 19:32
  • I'm still in the middle of the book. Its called jumping into c++ by alex allain. The excercises are difficult but rewarding. But i do feel like its a bit weird. – Rafał Myśliwczyk Aug 03 '17 at 19:34

1 Answers1

1

The correct solution is to not use a manual array at all, use the STL's std::vector container instead, eg:

#include <vector>
#include <string>

struct Friend
{
    std::string name = "";
    int daysSinceContact = 0;
};

struct User
{
    std::string name = "";
    std::string password = "";
    std::vector<Friend> friends;
};

...

std::vector<User> usersInformation(5);

...

Friend newFriend;
newFriend.name = "John";
newFriend.daysSinceContact = 2;
usersInformation[0].friends.push_back(newFriend);

// Or simpler:
usersInformation[0].friends.emplace_back(Friend{"John", 2});

That being said, if you really want to manage the array manually, you need to do something more like this instead (which includes implementing the Rule of Five to prevent corrupting and leaking memory):

#include <string>

struct Friend
{
    std::string name = "";
    int daysSinceContact = 0;
};

struct User
{
    std::string name = "";
    std::string password = "";
    int numberOfFriends = 0;
    Friend *friends = new Friend[numberOfFriends];

    // default constructor (nothing extra that isn't already done above)
    User() = default;

    // copy constructor
    User(const User &src) :
        name(src.name),
        password(src.password),
        numberOfFriends(src.numberOfFriends),
        friends(new Friend[numberOfFriends])
    {
        for(int i = 0; i < numberOfFriends; ++i)
            friends[i] = src.friends[i];
    }

    // move constructor
    User(User &&src) :
        name(std::move(src.name)),
        password(std::move(src.password)),
        numberOfFriends(numberOfFriends),
        friends(src.friends)
    {
        src.friends = nullptr;
        src.numberOfFriends = 0;
    }

    // destructor
    ~User()
    {
        delete[] friends;
    }

    // copy assignment operator
    User& operator=(const User &src)
    {
        if (this != &src)
        {
            Friend *newFriends = new Friend[src.numberOfFriends];
            for(int i = 0; i < src.numberOfFriends; ++i)
                newFriends[i] = src.friends[i];

            name = src.name;
            password = src.password;

            delete[] friends;
            friends = newFriends;
            numberOfFriends = src.numberOfFriends;
        }

        return *this;
    }

    // move assignment operator
    User& operator=(User &&src)
    {
        name := std::move(src.name);
        password = std::move(src.password);

        Friend *oldFriends = friends;
        friends = src.friends;
        src.friends = oldFriends;

        int oldNumber = numberOfFriends;
        numberOfFriends = src.numberOfFriends;
        src.numberOfFriends = oldNumber;

        return *this;
    }

    // addition helper
    void addFriend(const std::string &name, int daysSinceContact = 0)
    {
        Friend *newFriends = new Friend[numberOfFriends + 1];
        for(int i < 0; i < numberOfFriends; ++i)
            newFriends[i] = friends[i];

        newFriends[numberOfFriends].name = name;
        newFriends[numberOfFriends].daysSinceContact = daysSinceContact;

        delete[] friends;
        friends = newFriends;
        ++numberOfFriends;
    }
};

Or this, which is slightly safer in terms of memory management:

#include <string>
#include <utility>
#include <algorithm>

struct Friend
{
    std::string name = "";
    int daysSinceContact = 0;
};

struct User
{
    std::string name = "";
    std::string password = "";
    int numberOfFriends = 0;
    Friend *friends = new Friend[numberOfFriends];

    // default constructor (nothing extra that isn't already done above)
    User() = default;

    // initializing constructor
    User(int initialCapacity) :
        friends(new Friend[initialCapacity])
    {
    }

    // copy constructor
    User(const User &src) :
        User(src.numberOfFriends),
        name(src.name),
        password(src.password),
        numberOfFriends(src.numberOfFriends)
    {
        std::copy(src.friends, src.friends + src.numberOfFriends, friends);
    }

    // move constructor
    User(User &&src) :
        name(std::move(src.name)),
        password(std::move(src.password)),
        numberOfFriends(0),
        friends(nullptr)
    {
        std::swap(friends, src.friends);
        std::swap(numberOfFriends, src.numberOfFriends);
    }

    // destructor
    ~User()
    {
        delete[] friends;
    }

    // copy assignment operator
    User& operator=(const User &src)
    {
        if (this != &src)
            User(src).swap(*this);

        return *this;
    }

    // move assignment operator
    User& operator=(User &&src)
    {
        src.swap(*this);
        return *this;
    }

    // swap helper
    void swap(User &other)
    {
        std::swap(name, other.name);
        std::swap(password, other.password);
        std::swap(numberOfFriends, other.numberOfFriends);
        std::swap(friends, other.friends);
    }

    // addition helper
    void addFriend(const std::string &name, int daysSinceContact = 0)
    {
        User temp(numberOfFriends + 1);

        std::copy(friends, friends + numberOfFriends, temp.friends);
        temp.friends[numberOfFriends] = Friend{name, daysSinceContact};

        std::swap(friends, temp.friends);
        ++numberOfFriends;
    }
};

Either way, then you can do this:

User *usersInformation = new User[5];

...

usersInformation[0].addFriend("John", 2);

...

delete[] usersInformation;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks! This is really helpfull and interesting. Thank you very much for your effort! :) – Rafał Myśliwczyk Aug 03 '17 at 20:42
  • Hi @RafałMyśliwczyk if this or any answer has solved your question please consider [accepting it](https://meta.stackexchange.com/q/5234/179419) by clicking the check-mark. This indicates to the wider community that you've found a solution and gives some reputation to both the answerer and yourself. – Konstantinos Kamaropoulos Aug 03 '17 at 23:25