Best way to modify a variable in a class

Pages: 123
Standard library containers (and utilities for smart pointers) do memory allocation on our behalf, so not for "any kind of".

jonnin wrote:
pointers can acquire new blocks of memory (that represent an object instance or variable).
a pointer also will allocate
A pointer must be resized manually

I would not say it like that. A pointer does exactly one thing: holds an address. It does not "get", "allocate", nor "resize".

We can allocate memory dynamically for objects. (And not really "we", but something something.)
Unlike variables, such allocated block of memory has no name that we could use; all we get from allocation is an address.
Address is a value. Value that we (must) store somewhere. In pointer variable.

you have to allocate new space of the new size, copy the old data to the new location, and surrender the old data's memory
... its a great place to mess up an break your program badly

This is spot on. The "you/we" has to write the code.
When we use vector, we use code that is already written (and probably rather good quality).

a vector will return any memory it used to the operating system when it goes out of scope or under various other conditions.

Yes. A vector is a custom type with destructor that deallocates the dynamic memory block that the vector "owns" on the "death of vector".
Similarly, smart pointers are custom types with destructor that deallocates the dynamic memory block that the smart pointer object "owns".

A raw pointer is as simple type as an int; it does nothing when its life ends.
That is fair. I didn't say it well but yes, the coder is writing lots of high risk, hands-on code when using pointers (even smart ones), code that probably could and should have been avoided.

------------------
when to use pointers? When you get one from a library, or need to send one to a library (a lot of cross-language tools use C strings for example). When you interface to C code. When you deal with some types of polymorphism. When you want to avoid copying fat objects (like your attacks, if you had a fixed list of 20 used by 500 characters). Just a few of the big ones...

but the problem is the question. Pointers can do a bunch of different things, and its important whether you are talking about dynamic memory vs polymorphism vs reference-like uses vs C code and so on.

The question even transcends pointers. Its like asking 'how would you know about vectors' if you were taught to use arrays in an old C++ book or from someone with an outdated skill set. Its like that... for most people, most of the time, the way you learn there is a better way is either by doing it wrong and getting peer reviewed or by stumbling across it in someone else's code or reading up.
C++ is a constantly updating language (every 3 years) and hence requires effort (quite a bit) to learn and to keep up-to-date. No book is going to cover the whole language and how to use it 'properly'. I came to C++ via Assembler (PDP-11), Pascal and then c. I was taught the Pascal language in a few weeks (it's really a quite simple language). The rest of the course was how to use the language (eg lists, data processing etc).

In C++ there is no problem using a non-owning raw pointer as required. The issue is with owning-pointers. This is where you use managed pointers so that memory is automatically released as needed.

But the question as to when to use a pointer at all (including managed) really comes from experience, reading blogs/articles/books etc and knowing the alternative options and their pros/cons. Note that not everything you read on the internet or older books may be good practice now for the latest C++ versions. There's load of C++ available that comes from C++ 98 or even earlier. You really need knowledge to know which is 'good' and which is 'bad.
I appreciate the responses, as with most stuff I have learned throughout the years, coming into contact with actual scenarios where the feature is genuinely needed helps me learn a ton, so during my game programming I'm sure i may encounter cases where i may need to use pointers.
I did have a question about polymorphism since we mentioned it earlier. If we have a polymorphic class, say Animal, and we have a Cat child class, what happens if we have a child class that has unique methods that only apply to that child class? downcasting is looked down upon so what do we do? I understand that if we have a polymorphic class we can just have a generic function like Speak() or Walk and make those virtual, then we can create unique definitions for them in the child classes that inherit it, but what happens when the child class has methods that do not fit for any other animal type? would you still just put it in the Animal base class?
When would you call those functions?
Well if one class, say the cat class has a function that is only specific to Cats, then anytime its necessary to call it. Perhaps i misunderstand the point of polymorphism though. If the goal of polymorphism is just simply to create in interface of functions that will be overwritten by the child classes then i can understand that pretty simply, but sometimes with inheritance you have classes that perform actions that are only specific to that class, so im unsure how to navigate that.
If you want to call a function via polymorphism then the base class has to have a definition of that function (unless you use a pure virtual class [abstract] where the base has no definition [base definition is set to = 0] but each derived class has to). A derived class can have their own specific functions but these can't be called via polymorphism but only via the class direct. Consider:

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
#include <iostream>

struct Base {
        virtual ~Base() {}
	virtual void hello() const {
		std::cout << "Hello from Base\n";
	}
};

struct Derv : public Base {
	void hello() const override {
		std::cout << "Hello from Derv\n";
	}

	void bye() const {
		std::cout << "Bye from Derv\n";
	}
};

void proc(const Base& cls) {
	cls.hello();
	//cls.bye();	// NO. Base doesn't have a member bye()
}

int main() {
	Base b;
	Derv c;

	proc(b);
	proc(c);

	c.bye();         // OK. using Derv class direct
}


displays:

1
2
3
Hello from Base
Hello from Derv
Bye from Derv


Last edited on
Lets make the "proc" a bit different:
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
46
47
48
49
50
#include <iostream>

struct Visitor;

struct Base {
	virtual void hello() const {
		std::cout << "Hello from Base\n";
	}
	virtual void apply( const Visitor& v ) const;
};

struct Derv : public Base {
	void hello() const {
		std::cout << "Hello from Derv\n";
	}

	void bye() const {
		std::cout << "Bye from Derv\n";
	}
	void apply( const Visitor& v ) const;
};

struct Visitor {
    void visit( const Base* b ) const {
        b->hello();
    }
    void visit( const Derv* b ) const {
        b->hello();
        b->bye();
    }
};

void Base::apply( const Visitor& v ) const {
    v.visit( this );
}

void Derv::apply( const Visitor& v ) const {
    v.visit( this );
}

int main() {
	Base b;
	Derv c;
	Visitor e;
	b.apply( e );
	c.apply( e );
	std::cout << "===\n";
	Base& p = c;
	p.apply( e );
}

Prints:
Hello from Base
Hello from Derv
Bye from Derv
===
Hello from Derv
Bye from Derv


Did I "downcast"?
struct Vistor visit() function is overloaded so which function version is used depends upon the type of the argument - and hence what the function actually does.
So im a bit confused, so in your examples, I don't need to downcast in order to access a child classes non virtual method? I asked AI for assistance with a downcasting function as my knowledge of pointers and templates is pretty weak at the moment but I understand whats happening here mostly.

What about something like this:

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <iostream>
#include <string>
#include <vector>
#include <memory>

class Animal
{
    public:
        Animal(const std::string& name, int age) : mName(name), mAge(age)
        {}

        std::string GetName() const
        {
            return mName;
        }

        int GetAge() const
        {
            return mAge;
        }

        virtual void Speak() = 0;
        virtual void Move() = 0;
        virtual void Sleep() = 0;

    private:
        std::string mName{ "" };
        int mAge{ 0 };

};

class Cat : public Animal
{
    public:
        Cat(const std::string& name, int age) : Animal{name, age}
        {}

        Cat() = default;

        void Speak() override
        {
            std::cout << "Meow!\n";
        }

        void Move() override
        {
            std::cout << "A cat moves like this\n";
        }

        void Sleep() override
        {
            std::cout << "A cat sleeps like this\n";
        }
        
        void SomethingUniqueToCats()
        {
            std::cout << this->GetName() << " Does something Unique\n";
        }

    private:
};

template<typename Derived, typename Base>
std::unique_ptr<Derived> Downcast(std::unique_ptr<Base>& basePtr) 
{
    if (Derived* result = dynamic_cast<Derived*>(basePtr.get())) 
    {
        return std::unique_ptr<Derived>(static_cast<Derived*>(basePtr.release()));
    }
    else 
    {
        return nullptr;
    }
}

int main()
{
    std::unique_ptr<Animal> cat = std::make_unique<Cat>("Wiskers", 2);

    std::cout << cat->GetName() << " is " << cat->GetAge() << " years old, and it says ";
    cat->Speak();

    Downcast<Cat>(cat).get()->SomethingUniqueToCats();
}
Note that you don't need constructor for Cat if you use a using statement. If you're using virtual then you should have a virtual destructor in the base class.

Also why unique_ptr in Downcast? The only place you should be using unique_ptr is in main so that cat is a memory owner. No other code requires a memory owning pointer. Also, you need to check the return from Downcast as it may be nullptr and you can't de-reference a nullptr. Note that dynamic_cast returns a nullptr if it can't cast so there's no need to check in Downcast.

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <iostream>
#include <string>
#include <memory>

class Animal
{
public:
	Animal(const std::string& name, int age) : mName(name), mAge(age) {}
	virtual ~Animal() {}

	std::string GetName() const {
		return mName;
	}

	int GetAge() const {
		return mAge;
	}

	virtual void Speak() const = 0;
	virtual void Move() const = 0;
	virtual void Sleep() const = 0;

private:
	std::string mName;
	int mAge { };

};

class Cat : public Animal
{
	using Animal::Animal;

public:
	void Speak() const override {
		std::cout << "Meow!\n";
	}

	void Move() const override {
		std::cout << "A cat moves like this\n";
	}

	void Sleep() const override {
		std::cout << "A cat sleeps like this\n";
	}

	void SomethingUniqueToCats() const {
		std::cout << GetName() << " Does something Unique\n";
	}
};

template<typename Derived, typename Base>
Derived* Downcast(Base* basePtr) {
	return dynamic_cast<Derived*>(basePtr);
}

int main() {
	const std::unique_ptr<Animal> cat { std::make_unique<Cat>("Whiskers", 2) };

	std::cout << cat->GetName() << " is " << cat->GetAge() << " years old, and it says ";
	cat->Speak();

	if (const auto ptr { Downcast<Cat>(cat.get()) }; ptr != nullptr)
		ptr->SomethingUniqueToCats();
	else
		std::cout << "Can't process unique!\n";
}

Last edited on
Registered users can post here. Sign in or register to post.
Pages: 123