How to let a variable be dependent on other variables inside a class?
Asked Answered
S

6

6

What is wrong with the variable international_standard_book_number? How can I make it that it changes, whenever isbn_field_i changes?

#include <iostream>
#include <string>

class ISBN
{
private:
  unsigned int isbn_field_1 = 0;
  unsigned int isbn_field_2 = 0;
  unsigned int isbn_field_3 = 0;
  char digit_or_letter = 'a';
  std::string international_standard_book_number =
    std::to_string(isbn_field_1) + "-" + std::to_string(isbn_field_2) + "-" +
    std::to_string(isbn_field_3) + "-" + digit_or_letter;

public:
  ISBN()
  {
    isbn_field_1 = 0, isbn_field_2 = 0, isbn_field_3 = 0, digit_or_letter = 'a';
  }
  ISBN(unsigned int a, unsigned int b, unsigned int c, char d)
  {
    isbn_field_1 = a, isbn_field_2 = b, isbn_field_3 = c, digit_or_letter = d;
  }
  friend std::ostream& operator<<(std::ostream& os, ISBN const& i)
  {
    return os << i.international_standard_book_number;
  }
};

int
main()
{
  ISBN test(1, 2, 3, 'b');
  std::cout << test << "\n";
  return 0;
}

Output:

0-0-0-a

Desired output:

1-2-3-b

Edit: This question aims at something else(why, instead of how) than mine, and its answers don't help me as much as the answers from this topic.

Scrannel answered 12/3, 2020 at 10:24 Comment(1)
Does this answer your question? Why should I prefer to use member initialization list?Mccrory
M
9

What is wrong with the variable international_standard_book_number? How can I make it that it changes, whenever isbn_field_i changes?

Generally speaking: you have to reassign it every time one component changes.

In your particular case: change the constructor using initialization list.

I mean... instead

ISBN(unsigned int a, unsigned int b, unsigned int c, char d)
 {isbn_field_1=a, isbn_field_2=b, isbn_field_3=c, digit_or_letter=d;};

write

ISBN(unsigned int a, unsigned int b, unsigned int c, char d)
 : isbn_field_1{a}, isbn_field_2{b}, isbn_field_3{c}, digit_or_letter{d}
{}

Now your example code write

1-2-3-b

What changes ?

With

ISBN(unsigned int a, unsigned int b, unsigned int c, char d)
 {isbn_field_1=a, isbn_field_2=b, isbn_field_3=c, digit_or_letter=d;};

first your fields are default initialized, so

isbn_field_1    = 0;
isbn_field_2    = 0;
isbn_field_3    = 0;
digit_or_letter = 'a';

international_standard_book_number="0"+"-"+"0"+"-"+"0"+"-"+'a';

then is executed the body of the constructor

isbn_field_1    = 1;
isbn_field_2    = 2;
isbn_field_3    = 3;
digit_or_letter = 'b';

but international_standard_book_number remain unchanged.

With

ISBN(unsigned int a, unsigned int b, unsigned int c, char d)
 : isbn_field_1{a}, isbn_field_2{b}, isbn_field_3{c}, digit_or_letter{d}
{}

the initialization list initialize the fields (and substitute the default initialization)

isbn_field_1    = 1;
isbn_field_2    = 2;
isbn_field_3    = 3;
digit_or_letter = 'b';

and then is executed the default initialization of international_standard_book_number but using the new values, so

international_standard_book_number="1"+"-"+"2"+"-"+"3"+"-"+'b';
Mcclean answered 12/3, 2020 at 10:32 Comment(2)
Is everything after ":" and before the body of the constructor an intializing list? Or can it be something else too?Scrannel
@Scrannel - well... after : and before the body of the constructor is (every initialization in it is part of the) initializer list or (I don't know if can be call it initializer list, in this case) a call to a delegate constructor (see an example in Ayxan answer).Mcclean
B
5

Use a member function.

#include <iostream>
#include <string>

class ISBN
{
private:
    unsigned int isbn_field_1=0;
    unsigned int isbn_field_2=0;
    unsigned int isbn_field_3=0;
    char digit_or_letter='a';
    std::string international_standard_book_number() const {
        return std::to_string(isbn_field_1)+"-"+std::to_string(isbn_field_2)+"-"+std::to_string(isbn_field_3)+"-"+digit_or_letter;
    }
public:
    ISBN(){isbn_field_1=0, isbn_field_2=0, isbn_field_3=0, digit_or_letter='a';}
    ISBN(unsigned int a, unsigned int b, unsigned int c, char d){isbn_field_1=a, isbn_field_2=b, isbn_field_3=c, digit_or_letter=d;};
    friend std::ostream &operator<<(std::ostream &os, ISBN const &i) 
    { 
        return os << i.international_standard_book_number();
    }
};


int main()
{
    ISBN test(1,2,3,'b');
    std::cout << test << "\n";
    return 0;
}

Variables in c++ use value sematics. When you do

std::string international_standard_book_number=
std::to_string(isbn_field_1)+"-"+std::to_string(isbn_field_2)+"-"+std::to_string(isbn_field_3)+"-"+digit_or_letter;

it will assign a value to international_standard_book_number based on the values that isbn_field_n has right now. It does not create some kind of automatic link between these variables that make sure they stay in sync.

If you want that behaviour you would have to make sure you update international_standard_book_number everytime one the the other fields are updated.

Burnedout answered 12/3, 2020 at 10:28 Comment(5)
I don't think this really answers the question, OP wants to have a string member initialized from other members in the class. Of course you can have a member function instead, but that is a different design. You could also assign the string value in the constructor, but I believe OP wants to know why the posted code does not work as expected.Nonagenarian
@jdehesa OP asks "How can I make it that it changes, whenever isbn_field_i changes?", not only after initialization, for which one should use a member function.Livonia
@idclev463035818 Reading it again, it's true that I can be interpreted like that, I'm not sure if the question means "whenever the parameters to the constructor change" or "whenever the values of the member variables of a constructed object change".Nonagenarian
@jdehesa The question simply states What is wrong with the variable international_standard_book_number?. Looking at OPs code it seems that he is expecting changes to isbn_field_n to propagate to international_standard_book_number which is the behaviour you would most easily get from using a member function.Burnedout
@Burnedout That's true, but the member variables are private and cannot be changed, so the object is effectively immutable... anyway there are answers covering the different interpretations now I think so OP will probably find what they need.Nonagenarian
L
2

If you only need to set the value once (e.g. the other values don't change after the object got constructed) you can use an initializer list:

ISBN(unsigned int a, unsigned int b, unsigned int c, char d) 
    : isbn_field_1(a), 
      isbn_field_2(b),
      isbn_field_3(c),
      digit_or_letter(d),
      international_standard_book_number(
          std::to_string(isbn_field_1) + "-" + 
          std::to_string(isbn_field_2) + "-" + 
          std::to_string(isbn_field_3) + "-" + 
          digit_or_letter)
{};

But keep in mind, that the member are still initialized in the order they are declared, not in the order of the initializer list.

Technically, you don't need to initialize international_standard_book_number in the initializer list, as max66's answer shows, it's a question of personal preference.

Lactobacillus answered 12/3, 2020 at 10:32 Comment(0)
H
2

Maintain class invariants (depends vars) it is what you have to code manually. It is one of reasons why we need classes. In a class you may forbid direct changes of members (make them private), but when they are changed via for instance special set methods update invariants accordingly.

E.g.

void set_field_1(int field) {
  isbn_field_1 = field;
  international_standard_book_number = std::to_string(isbn_field_1)+"-"+std::to_string(isbn_field_2)+"-"+std::to_string(isbn_field_3)+"-"+digit_or_letter; 
}
Holyhead answered 12/3, 2020 at 10:38 Comment(0)
M
2

I want to add to @max66's answer.

Usually you have a hierarchy of constructors calling each other and one final final "master" constructor that takes all arguments and initializes the variables. This avoids code duplication and vastly simplifies which constructors initialize what. You can see what I mean in the below example. Besides that, to do proper string formatting in a readable manner, use the {fmt} library:

#include <fmt/format.h>
#include <string>

class ISBN
{
private:
        unsigned int isbn_field_1;
        unsigned int isbn_field_2;
        unsigned int isbn_field_3;
        char digit_or_letter;
        std::string international_standard_book_number;

public:
        ISBN()
                : ISBN{ 0, 0, 0, 'a' }
        {}
        ISBN(unsigned int a, unsigned int b, unsigned int c, char d)
                : isbn_field_1{ a }
                , isbn_field_2{ b }
                , isbn_field_3{ c }
                , digit_or_letter{ d }
                , international_standard_book_number{ fmt::format("{}-{}-{}-{}",
                                                                  isbn_field_1,
                                                                  isbn_field_2,
                                                                  isbn_field_3,
                                                                  digit_or_letter) }
        {}
};
Mccrory answered 12/3, 2020 at 11:38 Comment(1)
ah, so variables should only be declared but not be set, and instead the default constructor sets the default values. is that right? also thanks for mentioning {fmt}!Scrannel
J
2

This code in Visual Studio 2019 at least works:

#include <iostream>
#include <string>

class ISBN
{
private:
    unsigned int isbn_field_1 = 0;
    unsigned int isbn_field_2 = 0;
    unsigned int isbn_field_3 = 0;
    char digit_or_letter = 'a';
    std::string international_standard_book_number =
        std::to_string(isbn_field_1) + "-" + std::to_string(isbn_field_2) + "-" +
        std::to_string(isbn_field_3) + "-" + digit_or_letter;

public:
    ISBN(unsigned int a, unsigned int b, unsigned int c, char d)
        :isbn_field_1(a), isbn_field_2(b), isbn_field_3(c), digit_or_letter(d), international_standard_book_number(std::to_string(isbn_field_1) + "-" + std::to_string(isbn_field_2) + "-" +
            std::to_string(isbn_field_3) + "-" + digit_or_letter)
    {
    }
    friend std::ostream& operator<<(std::ostream& os, ISBN const& i)
    {
        return os << i.international_standard_book_number;
    }
};

int
main()
{
    ISBN test(1, 2, 3, 'b');
    std::cout << test << "\n";
    test = {2, 3, 4, 'c'};
    std::cout << test << "\n";
    return 0;
}

Also, why the empty constructor?

Jedidiah answered 12/3, 2020 at 13:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.