Class Questions

Pages: 12
Ill try, but if you have to visualize everything in order to think, you are going to have a long, hard road ahead of you.

You have a bunch of shoeboxes. They each hold different stuff. you can put a label on each one saying what to do with the contents. The contents may have labels as well, which may refer to items in other shoeboxes, but because it is all well labeled, what to do with each part is 100% clear.


Getting back to pointers vs. references. a reference is a synonym for another object. In other words, it's just a different name for some object.

A pointer is a type of variable that contains the address of another object.

A reference may be implemented with a pointer, but it doesn't have to be:
1
2
int A;  // compiler knows where A is
int &B(A);  // Compiler knows where B is too. No need for a pointer. 


When you think of a reference as a synonym for another object, some of the subtle points become obvious:
- taking the address of a reference takes the address of the other object.
- Assigning something to a reference changes the object.
- You can't change a reference to refer to another object, anymore than you can change int x to somehow refer to a different integer.

And since a pointer is a variable like any other:
- taking the address of a pointer takes the address of the pointer itself, not the thing it points to.
- Assigning something to a pointer causes it to point to somewhere else.
- The above means that you can change a pointer to point to different objects at runtime.

Trying to understand C++ by analogies to the real world is a fool's errand. You can go a small way like that, but the flaws in the analogies will lead to mistakes, and when you find yourself trying to come up with an analogy for the Curiously Recurring Template Pattern... well.

Abandon analogies. Understand the C++ model as defined by the spec in and of itself, understand the simple model of the real world hardware, and trust the implementer to have done a good job.

so storing a word in a variable is equal to putting a shoe in a box

Except that the box already had a shoe in it, and it's impossible to make an empty box. This analogy is already not working out.
Last edited on
a reference is a synonym for another object. In other words, it's just a different name for some object.

When you think of a reference as a synonym for another object, some of the subtle points become obvious:
- taking the address of a reference takes the address of the other object.
- Assigning something to a reference changes the object.
- You can't change a reference to refer to another object, anymore than you can change int x to somehow refer to a different integer.


It's a different name?


I've always been told that doing this:

void SomeFunction(int x)

makes a copy of the variable, whereas this

void SomeFunction(int& x)

Your actually passing the variable itself, I understand a pointer is just a variable that holds a memory address, and using pointers is good for things that will use a large amount of memory like for example in a game, holding a vector of images, you would want to use a pointer for that and access the images locations directly instead of just using the vector and loading them all and slowing the program down, but a reference, I dont quite understand. Does a reference used in function parameters like my example above function differently that this:

Base& = derived;

??
Last edited on
It's a different name?

Yes.

void foo( Bar & kettle );
The foo() is called by N different functions and they pass M different Bar arguments to it.
The foo() does not know the names used of the callers, but it calls the current caller's object "kettle".

1
2
Foo pot;
Bar & kettle = pot;

Although we know in this code that kettle is pot, we call it kettle anyway.
The object has type Foo and behaves like Foo, but when we call it kettle, we have to treat it like a Bar.

1
2
Foo water;
Bar * kettle = &water;

The kettle is not water. The kettle is just a note about where we can find the water.
The water behaves as Foo does, but if we find it via the kettle, the rules of Bar apply to handling the object.


Reference is a different name.
Pointer is a separate object.
It is not our problem, how the compiler implements pointers and references.
Ok, so when you modify a variable by reference your actually modifying the variable itself indirectly, so conceptually it's like a class setter, you dont want to modify the variable directly so you use a middle-man, a setter function, to do that job which will modify that variable indirectly, correct?
Ok, so when you modify a variable by reference your actually modifying the variable itself indirectly, so conceptually it's like a class setter, you dont want to modify the variable directly so you use a middle-man, a setter function, to do that job which will modify that variable indirectly, correct?


That doesn't seem to make any sense.

When you modify a variable by reference, you're modifying the variable because a reference is another name for the exact same object. There is no middle-man.

so when you modify a variable by reference your actually modifying the variable itself indirectly

No. Not indirectly. Directly.
AH!, I understand now, thanks!
So when I see this:


Derived derived;
Base& base = derived;

What its actually doing is treating base as a derived object, and this:

Derived derived;
Base* p_base = &derived;

Is pointing to the memory address of derived.

Am I correct so far?
Last edited on
So when I see this:

1
2
Derived derived;
Base& base = derived;


What its actually doing is treating base as a derived object


No. It's creating a Derived object, called derived, then creating an alias for that object called base.

However, if you use base to access the object, it will treat it as if it were a Base object: that is, only the interface defined for the Base class will be available, not the interface for the Derived class.

and this:

1
2
Derived derived;
Base* p_base = &derived;

Is pointing to the memory address of derived.

Yes - p_base stores the memory address of derived.

As with the reference, if you use p_base to access the object, it will treat it as if it were a Base, i.e. only the Base interface will be available.
Last edited on
Derived derived;
Base& base = derived;

Using similar names for both types and variables makes example unnecessarily confusing.


1
2
Derived foo;
Base& bar = foo;

There is one object and the type of that object is Derived.
There are two names in your code that both refer to that one object.

A Derived IS-A Base. The Derived can have (additional) interface that the Base does not.
Name bar has access only to the Base-portion of the object's interface.
Name foo has access to all of the (Derived) object's interface.


new Derived;
There is one object and the type of that object is Derived.
There are no names in the code for the object. We cannot use the object in any way, not even deallocate it.


1
2
Derived foo;
Base* gaz = &foo;

There are two objects. One has type Derived and another has type pointer to Base
.
There are two names in the code. One for each object.
By dereferencing pointer object gaz you can access the Base-portion of the interface of object foo.
Name foo has access to all of the (Derived) object's interface.


Base* snafu = new Derived;
There are two objects. One has type Derived and another has type pointer to Base
.
There is one name in the code. The name of the pointer object.
By dereferencing pointer object snafu you can access the Base-portion of the interface of the unnamed object.
No. It's creating a Derived object, called derived, then creating an alias for that object called base.


I meant to say that, my apologies, I got it backwards. I've been re-reading it over and over the past day, both your and keskiverto's response and it makes sense now, I've added it to my notes.

So on something different, my first thing I asked was if accessing variables in a derived class was best the way I did it here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>

class Parent
{
    public:
        Parent(int x, std::string str) : m_x(x), m_str(str) {}
        virtual void Speak()
        {
            std::cout << "I am parent class!\n";
        }

        virtual void Test() = 0;

    protected:
        int m_x = {};
        std::string m_str = {};
};

class Child : public Parent
{
    public:
        Child(int x, std::string str) : Parent(x, str) {}
        void Speak() override
        {
            std::cout << "I am child class!\n";
        }

        void Test() override
        {
            std::cout << "Forced to be Overridden in child\n";
        }
};

void Output(Parent& parent)
{
    parent.Speak();
    parent.Test();
}

int main()
{
    Child child(8, "Ben");

    Output(child);
}


So I know I was told there really is no "Best" way, only tradeoffs based on what you want to accomplish. My questions are these:

If I have 2 constructors, constructed like this:

Base(int x, int y) : m_x(x), m_y(y){}

and this

1
2
3
4
5
Base(int x, int y)
{
    m_x = 6;
    m_y = 7;
}


Also this:

1
2
3
4
5
6
7
class Base
{

   private:
       int x = 8;
       int y = 9;
}


What is the difference between all these? does any of these cause problems with using them between classes or with objects in other functions? or any problem they may cause that I cant think of. If it helps any, my main reason for learning C++ is game design, thats my end goal and it's what I want to achieve. 2D game design to be exact, im not sure if that helps narrow down how I want to use classes and c++ itself.
Last edited on
This,
1
2
3
4
5
Base(int x, int y)
{
    m_x = x;
    m_y = y;
}

Is essentially this:
1
2
3
4
5
6
Base(int x, int y)
 : m_x(), m_y()
{
    m_x = x;
    m_y = y;
}


Simpler code with same essence:
1
2
int m_x;
m_x = x;

There is first (default) initialization of variable(s), and only after that (copy) assignment(s).

The point is that by the time that the body of the constructor starts:
1
2
Base(int x, int y)
{ // here 

all the base class(es) and all the members have already been properly constructed.
In the body you use the members.


Why write
1
2
int m_x;
m_x = x;

when you can do:
int m_x = x;

You cannot do
1
2
3
4
5
6
7
int& foo;
foo = x;

// or

const int bar;
bar = x;

because assignment is not initialization.


You can do
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int& foo = x;
const int bar = x;

// and

class Base {
  int& m_foo;
  const int m_bar;
public:
  Base( int x, int y )
  : m_foo(x), m_bar(y)
  {
  }
};

So, to summarize what keskiverto said, the first of your three possibilities is usually the best, because it initialises the data members directly to the values you want, rather than default-initialising them, and then afterwards assigning values to them.

The third of your three snippets is doing something different - it's specifying the default values that those members will be initialised to if you don't initialise them to anything else.
relevant reading in the C++ Core Guidelines:

C.48: Prefer in-class initializers to member initializers in constructors for constant initializers
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rc-in-class-initializer


C.49: Prefer initialization to assignment in constructors
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rc-initialize

and I suppose
C.45: Don't define a default constructor that only initializes data members; use in-class member initializers instead
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rc-default

to quote,
Example, bad
1
2
3
4
5
6
7
class X1 { // BAD: doesn't use member initializers
    string s;
    int i;
public:
    X1() :s{"default"}, i{1} { }
    // ...
};

Example
1
2
3
4
5
6
7
class X2 {
    string s = "default";
    int i = 1;
public:
    // use compiler-generated default constructor
    // ...
};
Last edited on
Topic archived. No new replies allowed.
Pages: 12