Jump to content

Several C++ questions. (Pointers & 2D arrays)

Guest
Go to solution Solved by Unimportant,
59 minutes ago, fpo said:

I'm guessing I forward declared incorrectly by using struct instead of class.

No, in C++ a struct and a class are the same, except for the fact that struct members and inheritance are public by default whereas they are private by default for class.

 

1 hour ago, fpo said:

Can you call functions based on a pointer?
IE

forward declare a class,

hold a pointer to a class not included in the header file

in CPP call a function part of the pointed to object's class.

 

I'm guessing the CPP would require an include to the header of the class that was forward declared.

 

Correct. As per the given example you can include the header of the forward declared class in the CPP file and then you can use it completely.

 

1 hour ago, fpo said:

Wouldn't a function containing a "dumb" pointer have an issue if an exception was thrown? Don't you have to "delete" it to clear its use?

The dumb pointer simply refers to a object that is owned elsewhere, so it can be accessed, but the owner is responsible for it's deletion. In the given smart pointer example the memory is owned by std::unique_ptr pThing in function SomeFunction. A dumb pointer to the managed object is passed to function DoSomething so that DoSomething can access the object, but the ownership and responsibility remains with pThing in SomeFunction. If DoSomething throws then the stack will be unwound until someone tries to catch the exception. It's not being caught in DoSomething so that function will exit. It's not being caught in SomeFunction either, so that function will exit as well, but because pThing is a automatic variable in SomeFunction it will go out of scope once that function exits and it's destructor will be called, which will delete the managed memory.

 

1 hour ago, fpo said:

In the above section, you said if "DoSomething" throws an exception, "pThing will automatically be deleted." Would this cause an error if you tried to use "pThing" after the exception is thrown? 

You can't even do that. If DoSomething throws the stack will be unwound until the exception is caught. SomeFunction will exit immediately and execution will resume at some lower level where the exception is caught.

 

1 hour ago, fpo said:

How do you make a smart pointer hold no value? IE "I am currently not pointing to anything" without completely destroying the pointer. Like how you can set a variable to "null" in some languages with the variable still existing & being able to be read, and written to.

How do you create a smart pointer without giving it a value? Or is that impossible with the RAII? I want all the "Tile" objects to point to nothing until a "Thing" tells a "Tile" that the "Tile" can point to the current "Thing" and then when a Thing leaves the Tile or is gameplay destroyed (like killing an enemy) to have Tile point to nothing.

A default constructed std::unique_ptr is empty. You can manually release it and it has a overload for operator bool to check if it's currently managing something:

std::unique_ptr<Thing> pThing;  //default constructed unique_ptr contains nullptr by default

if (pThing)  //operator bool
{
  //will not execute, pThing contains nullptr
}

pThing.reset(new Thing);  //Create new Thing and have pThing manage it (don't do this, use std::make_unique)

if (pThing)  //operator bool
{
  //will execute, pThing does not contain nullptr
}

pThing.release(); //Delete object under management, pThing free again.

HOWEVER The point of the story was that smart pointers are only to be used when you need to manage dynamically allocated memory. In your case that doesn't seem to be the case. If your array of Tiles has a size that is known at compile time, just make it a std::array (or your own Array2D that uses std::array under the hood). If the size is unknown at compile time then use std::vector (or your own Vector2D that uses std::vector under the hood).

 

1 hour ago, fpo said:

How do you pass a normal array to a function by reference?

A C style array decays to a pointer:

void
UseIntArray(int* array, int size)
{
	for (int i = 0; i < size; ++i)
	{
		array[i] = //do something...   
	}
}

int 
main()
{
	int array[10];
	UseIntArray(array, 10);
  
	return 0;  
}

 

1 hour ago, fpo said:

How do you pass a vector or array to a function by reference? Same way as an integer variable?

C++ actually supports references as a separate concept and those are preferred:

void
UseIntVector(std::vector<int>& vec)
{
	for (auto& elem : vec)
	{
		//Use elem   
	}
}

int
main()
{
	std::vector<int> vec;
	//populate vector...
	UseIntVector(vec);

	return 0;
}

But you could use pointers if you wanted to:

void
UseIntVector(std::vector<int>* pVec)
{
	for (auto& elem : *pVec)
	{
    	//Use elem
	}
}

int
main()
{
	std::vector<int> vec;
	//populate vector...
	UseIntVector(&vec);

	return 0;
}

A std::array is somewhat trickier as it's size is part of the template. So you'd either have to write your function to only accept std::arrays of fixed size or the function itself has to be a template:

template <std::size_t size>
void
UseIntArray(std::array<int, size>& array)
{
	for (auto& elem : array)
	{
		//Use elem...
	}
}

int
main()
{
  	std::array<int, 100> array;
  	UseIntArray(array); //Template deduction passes along size automatically.
  
	return 0;  
}

 

1 hour ago, fpo said:

Straight Stewie recommended the possibility of a 2D array by making an array contain arrays of type.


int size = 10;
int otherSize = 15;
std::array<std::array<Thing, size>, otherSize>

If I do this method (provided it's a good method to follow) how would I pass it to a function & how would I access specific indices in the function?

It's not recommended as it still is not guaranteed to be a continuous block of memory.

 

Many of your technical problems are artificial, stemming from poor overall design, so I'd focus my efforts there. For example, your "map" should probably be a class in it's own right, not a plain 2D array of Tiles. Then the "map" object manages it's implementation details internally and other objects ask the map to perform tasks. This keeps "map"'s implementation details restricted to the "map" class and stops it from being scattered all around the code as it is now.

 

In simple terms, The "Human" object asks a "Dog" object to walk. The "Human" object doesn't go and manipulate the dog's legs directly.

 

TL;DR

No Tl;dr today... sorry, too complicated for me to summarize.

 

Long:

I had some questions & just got buried in an info dump in a discord server. (Got buried in lots of way over my head stuff with little explanation.)

I have 2 classes.
`Tile.h` & `Thing.h`
`Tile` stores a pointer called occupant to a `Thing` to know what is in the tile.
`Thing` asks surrounding `Tile` on a global 2D array called map to execute a function.

bool Tile::GetHasOccupant() {
	if (occupant == nullptr)
		return false;
	return true;
}

The way `Thing` finds out if nearby tiles are occupied is by a function that passes the global 2D array by reference.

 

I had a problem where I was #include Thing in Tile.h and #include Tile in Thing.h

Someone told me I can forward declare by doing

struct Thing; //in Tile.h
struct Tile; // in Thing.h

This would supposedly save the compiler from endlessly including files recursively even if you use #pragma once (Working in Visual Studio 2017 if it matters)

 

In Thing.cpp I am executing a function by the following:

void Thing::RequestMove(Tile** array, int width, int height) {
	if (array[worldX][worldY].GetHasOccupant()) {
		printf("WORKING!");
	}
	else {
		printf("Wrong coordinates");
	}
}

 

I call the method like so in my main.cpp

Tile map[10][10];
std::vector<Thing> things;

int main(){

  //Some code... Not important but sets up predicament
	things.push_back(Thing(300, 35, Thing::circle, texture));
	//sf is a namespace from the SFML library. Vector2i is a class that stores 2 integers; x & y
	sf::Vector2i t1Loc = things[0].NormalizeWorldSpaceToGridSpace();
	map[t1Loc.x][t1Loc.y].occupant = &things[0];
  //End of setup code

things[0].RequestMove((Tile**)&map[0][0], 10, 10);
}

 

 

My current bug is shown here from Tile.cpp here:

Spoiler

image.png.f6e34e5c86dbb648d9178eb387587118.png

My debugger's local & autos shows the following:

Spoiler

image.png.b3194ad1d6f11c45795d57ebe7795985.png

 

QUESTIONS

That's my problem.
Now... what I've been told (aside from everything I'm doing is wrong)

1. Don't use "C style arrays. Use STL containers; IE use std::array"

2. Don't use normal pointers. Use smart pointers.

 

What wasn't answered:
3. What is going wrong?

4. How do I make "C style arrays" work?

5. If "C style arrays" are completely unusable (which I doubt) how do I use std::array? None of my C++ textbooks touch on it & I can't find a resource online. I want to use a 2D array, not a 1D array.

6. How do I make an original pointer work? (some called it "C style pointers."

7. Even though I'm not there yet.... Where can I learn how to use smart pointers? These aren't covered nicely in any of my resources.

 

I don't like the computer doing things for me. One of the reasons I am using C++. I want to have the control not apparent in other languages. It's okay if I break the computer, but I want to know why.
I think these are complex topics & people just kinda found the way that works for them, so they never bothered learning any other way & that's why it's so difficult to understand what they're saying.

 

EDIT:
Source codes:
Thing.h

Spoiler
Spoiler

#pragma once
#include <SFML/Graphics.hpp>
#include "Tile.h"
class Thing {
public:

	enum Collider { circle, square };

	Thing(int wX, int wY, Collider col, sf::Texture &texture);

	sf::Vector2i NormalizeWorldSpaceToGridSpace();
	void RequestMove(Tile** array, int width, int height);

  //SFML variable
	sf::Sprite sprite;

	//Location (NOT screenspace. Relative space.)
	int worldX;
	int worldY;

	//Physics (used for collisions.)
	int gridX;
	int gridY;

private:
	Collider collider;

};

 

Thing.cpp

Spoiler
Spoiler

#include "Thing.h"
#include "Globals.h"
#include <iostream>

Thing::Thing(int wX, int wY, Collider col, sf::Texture &texture) {
	worldX = wX;
	worldY = wY;
	collider = col; //enumeration
	sprite.setTexture(texture); //SFML API
}


sf::Vector2i Thing::NormalizeWorldSpaceToGridSpace() {
	gridX = worldX / TILEsIZE;
	gridY = worldY / TILEsIZE;
	printf("worldX: %d GridX: %d worldY: %d gridY: %d \n", worldX, gridX, worldY, gridY);
	return sf::Vector2i(gridX, gridY);
}

void Thing::RequestMove(Tile** array, int width, int height) {
	if (array[worldX][worldY].GetHasOccupant()) {
		printf("WORKING!");
	}
	else {
		printf("Wrong coordinates");
	}
}

 

Tile.h

Spoiler
Spoiler

#pragma once
//#include "Thing.h"
struct Thing;

class Tile{
public:
	Tile(int xArg, int yArg);
	Tile();
	int x;
	int y;
	bool GetHasOccupant();
	Thing * occupant;
};

 

Tile.cpp

Spoiler
Spoiler

#include "Tile.h"
#include "Thing.h"

Tile::Tile(int xArg, int yArg) {
	x = xArg;
	y = yArg;
	occupant = nullptr;
}

Tile::Tile() {
	x = -1;
	y = -1;
	occupant = nullptr;
}

bool Tile::GetHasOccupant() {
	if (occupant == nullptr)
		return false;
	return true;
}

 

Globals.h

Spoiler
Spoiler

#pragma once

const int TILEsIZE = 80;
const int COLrADIUS = 40;

 

main.cpp if you want to execute the program. It's messy as it's learning/test code for the most part.

void MapCreate() is what is relevant.

Spoiler
Spoiler

#include <SFML/Graphics.hpp>
#include "Thing.h"
#include "Globals.h"
#include "Tile.h"

sf::Vector2i playerPos = sf::Vector2i(0,0);
bool moveRight = false;
bool moveLeft = false;
bool moveUp = false;
bool moveDown = false;

void Update();
void MapCreate();

Tile map[10][10];
std::vector<Thing> things;

sf::Texture texture;


int main() {
	int screenX = 1920;
	int screenY = 1080;
	MapCreate();
	
	if (!texture.loadFromFile("G:/Thumbnails/1.1Tree-Fitty.png")) {
		std::printf("Failed to load texture! ");
	}
	texture.setSmooth(true);
	
	sf::Sprite sprite;
	sprite.setTexture(texture);
	//sprite.setScale(.5f, 1);
	sprite.setTextureRect(sf::IntRect(139,31,80,80));
	sprite.setOrigin(40, 40);
	sprite.setPosition(screenX/2, screenY/2);

	

	sf::Vector2i obPos = sf::Vector2i(5,100);
	sf::Sprite ob;
	ob.setTexture(texture);
	
	ob.setOrigin(texture.getSize().x/2, texture.getSize().y / 2);
	ob.setScale(.5f, .5f);
	ob.setPosition(obPos.x - playerPos.x, obPos.y - playerPos.y);
	sf::Vector2i mousePos;

	sf::RenderWindow window(sf::VideoMode(screenX, screenY), "Program");
	sf::CircleShape shape(100.f);
	shape.setFillColor(sf::Color::Green);

	sf::Clock clock;
	sf::Time timeBetweenFrames = sf::milliseconds(16);


	while (window.isOpen())
	{
		sf::Event event;
		while (window.pollEvent(event))
		{
			mousePos = sf::Mouse::getPosition(window);
			
			int xDiff = sprite.getPosition().x - mousePos.x;
			int yDiff = sprite.getPosition().y - mousePos.y;
			
			int hypotonuse = sqrt((xDiff * xDiff) + (yDiff * yDiff));

			//printf("X: %d Y: %d Hypot: %d\n", xDiff, yDiff, hypotonuse);
			float angle = (atan2(yDiff, xDiff) * 180 / 3.14159265f) - 90;
			sprite.setRotation(angle);

			if (event.type == sf::Event::Closed)
				window.close();

			
			if (event.type == sf::Event::KeyPressed) {
				if (event.key.code == sf::Keyboard::A)
					moveLeft = true;
				if (event.key.code == sf::Keyboard::D)
					moveRight = true;
				if (event.key.code == sf::Keyboard::W)
					moveUp = true;
				if (event.key.code == sf::Keyboard::S)
					moveDown = true;
			}
			if (event.type == sf::Event::KeyReleased) {
				if (event.key.code == sf::Keyboard::A)
					moveLeft = false;
				if (event.key.code == sf::Keyboard::D)
					moveRight = false;
				if (event.key.code == sf::Keyboard::W)
					moveUp = false;
				if (event.key.code == sf::Keyboard::S)
					moveDown = false;
			}
			
		}
		if (clock.getElapsedTime() >= timeBetweenFrames) {
			clock.restart();
			Update();
		}

		
		
		ob.setPosition(obPos.x - playerPos.x, obPos.y - playerPos.y);

		window.clear();
		window.draw(sprite);
		window.draw(ob);
		//window.draw(shape);
		window.display();
	}

	return 0;

}



void Update() {
	if (moveRight)
		playerPos.x += 1;
	if (moveLeft)
		playerPos.x -= 1;
	if (moveUp)
		playerPos.y -= 1;
	if (moveDown)
		playerPos.y += 1;
}

void MapCreate() {
	for (int x = 0; x < 10; x++) {
		for (int y = 0; y < 10; y++) {
			map[x][y] = Tile(x,y);
			printf("Mapx: %d Mapy: %d", map[x][y].x, map[x][y].y);
		}
	}


	//Create Thing & add to heirarchy
	things.push_back(Thing(300, 35, Thing::circle, texture));

	sf::Vector2i t1Loc = things[0].NormalizeWorldSpaceToGridSpace();
	map[t1Loc.x][t1Loc.y].occupant = &things[0];

	//map[3][0].occupant->Print();
	things[0].RequestMove((Tile**)&map[0][0], 10, 10);

}

 

 

Link to comment
Share on other sites

Link to post
Share on other sites

Can you show the declaration of those classes?

Don't ask to ask, just ask... please 🤨

sudo chmod -R 000 /*

Link to comment
Share on other sites

Link to post
Share on other sites

11 minutes ago, Sauron said:

Can you show the declaration of those classes?

Edited the OP at the bottom under:
"Edit"

Link to comment
Share on other sites

Link to post
Share on other sites

13 minutes ago, fpo said:

Edited the OP at the bottom under:
"Edit"

It seems to me your code assigns the value "nullptr" to the area of memory pointed to by the pointer, meaning it's not a nullptr but it doesn't point to what it's supposed to either. This is a really confusing way of going about things and it's quite unnecessary. You don't need to assign "nullptr" to the pointer for it to be null, it's implicit in the fact that you still didn't assign a value to it.

 

The check is as simple as:

class Tile{
public:
	Tile(int xArg, int yArg);
	Tile();
	int x;
	int y;
	bool GetHasOccupant();
	Thing * occupant;
};
Tile::Tile(int xArg, int yArg) {
	x = xArg;
	y = yArg;
}
bool Tile::GetHasOccupant() {
	if (occupant) return true;
	return false;
}

Some more info on this if you need it: https://stackoverflow.com/a/3825704/4153995

 

I should note this is not a smart pointer, that's something specific to C++ which you can learn about here.

59 minutes ago, fpo said:

4. How do I make "C style arrays" work?

The syntax is the same as C (as you might guess). They are not recommended because things like std::vector and std::array are safer and more flexible.

1 hour ago, fpo said:

5. If "C style arrays" are completely unusable (which I doubt) how do I use std::array? None of my C++ textbooks touch on it & I can't find a resource online. I want to use a 2D array, not a 1D array.

As per the reference, a 2x2 std::array of std::arrays of ints would be declared as:

std::array<std::array<int, 2>, 2> my_array = {{0, 0}, {0, 0}};

same for std::vector except you don't need to specify the size.

Don't ask to ask, just ask... please 🤨

sudo chmod -R 000 /*

Link to comment
Share on other sites

Link to post
Share on other sites

 

 
44 minutes ago, fpo said:

1. Don't use "C style arrays. Use STL containers; IE use std::array"

 

5. If "C style arrays" are completely unusable (which I doubt) how do I use std::array? None of my C++ textbooks touch on it & I can't find a resource online. I want to use a 2D array, not a 1D array.

I find it acceptable to use C style arrays inside of classes. You shouldn't handle them bare and pass them around as arguments, unless you have to, i.e. to interface with a graphics card.

A std::vector and a std::array is significantly slower than a C style array for many applications, and this is especially apparent when speed matters, i.e. in graphics.

 

Many standard library collection types are just abstractions over C style arrays, and rely on bare pointers for efficiency sake. Using them safely is possible under abstractions.

 

In C#, we can define multidimensional arrays as type[firstIndexSize, secondIndexSize]. For std::array we would define it as std::array<std::array<type, secondIndexSize>, firstIndexSize>. That is, it is a std::array with length firstIndexSize that contains std::arrays with length secondIndexSize that contains type.

 

46 minutes ago, fpo said:

I had a problem where I was #include Thing in Tile.h and #include Tile in Thing.h

Someone told me I can forward declare by doing

Use include guards instead:

// File foo.h
#ifndef Bar_H
#include "bar.h"
#define Bar_H
#endif
  
class foo
{
private:
  *bar _someBar;
}

// File bar.h
#ifndef Foo_H
#include "foo.h"
#define Foo_H
#endif
  
class bar
{
private:
  *foo _someFoo;
}

 

1 hour ago, fpo said:

7. Even though I'm not there yet.... Where can I learn how to use smart pointers? These aren't covered nicely in any of my resources.

Get a copy of TC++PL: The C++ Programming Language, by Bjarne Stroustrup.

Additionally, here is a quick introduction page: https://docs.microsoft.com/en-us/cpp/cpp/smart-pointers-modern-cpp?view=vs-2019

Use that page to get ideas of more specific things to look up. The most common types of smart pointers are unique_ptr and shared_ptr.

ENCRYPTION IS NOT A CRIME

Link to comment
Share on other sites

Link to post
Share on other sites

@fpo Way too much to answer from my phone but 2 red flags that stick out immediately: 

  • Handing out pointers to things stored in collections like a std::vector and storing them is a recepy for disaster. std::vector will eventually relocate and invalidate all those pointers.
  • things[0].RequestMove((Tile**)&map[0][0], 10, 10); 

is plain wrong. &map[0][0] is a *Tile, not a **Tile. Casting it to make the compiler shut up doesnt change that. This is undefined behavior.

Link to comment
Share on other sites

Link to post
Share on other sites

Sauron

 
 
 
 
 
 
4
 
 
 
 
 
 
 
5
Spoiler
3 hours ago, Sauron said:

It seems to me your code assigns the value "nullptr" to the area of memory pointed to by the pointer, meaning it's not a nullptr but it doesn't point to what it's supposed to either. This is a really confusing way of going about things and it's quite unnecessary. You don't need to assign "nullptr" to the pointer for it to be null, it's implicit in the fact that you still didn't assign a value to it.

I was told that was the way you made a pointer null so it has no value.

IE set value to "zero" without clearing the memory of the object being pointed to.

How would you "clear" a pointer's value without emptying the memory of the previously pointed to object?

 

3 hours ago, Sauron said:

The check is as simple as:

~snip~

Some more info on this if you need it: https://stackoverflow.com/a/3825704/4153995

That makes sense for a coding convention. Is my way incorrect? I like verbose, but if that's the way it's done, I can do it that way too.

 

3 hours ago, Sauron said:

I should note this is not a smart pointer, that's something specific to C++ which you can learn about here.

Yeah, I was using some "dumb" or "C style" pointers because I wanted to dictate all its actions.

I'm seeing in the unique_ptr section they only let one pointer point to a pointed object (tough phrasing)

It seems to be the type of pointer I am looking for as I want a Thing to only be seen as "in" one Tile at a time. If I'm understanding it correctly, it can "hijack" an object from another pointer automatically making the first one a "nullptr" and assigning the pointed to object to the new one that hijacked the object.

Is that how it works?
It would "save" me the logic of clearing the previous pointer.
I'm working on a game so, when things die, all I have to do is destroy the object & that should clear the pointer for me it seems?

 

3 hours ago, Sauron said:

The syntax is the same as C (as you might guess). They are not recommended because things like std::vector and std::array are safer and more flexible.

I meant, like how do I pass a C style array to a function by reference, so I'm operating on the array directly & not a copy of the array.

 

3 hours ago, Sauron said:

As per the reference, a 2x2 std::array of std::arrays of ints would be declared as:



std::array<std::array<int, 2>, 2> my_array = {{0, 0}, {0, 0}};

same for std::vector except you don't need to specify the size.

How do I access a specific index then with std::array?
In C arrays it's easy.

arr[x][y] like a 2D graph.

 

Straight Stewie

 

 
 
 
 
 
 
 
 
6
 
 
 
 
 
 
4
Spoiler
4 hours ago, straight_stewie said:

I find it acceptable to use C style arrays inside of classes. You shouldn't handle them bare and pass them around as arguments, unless you have to, i.e. to interface with a graphics card.

4 hours ago, straight_stewie said:


A std::vector and a std::array is significantly slower than a C style array for many applications, and this is especially apparent when speed matters, i.e. in graphics.

This code is actually for a game I'm working on. I need these arrays to do "spatial awareness" for my physics engine design on as many objects that are moving, 60 times per second.

IE: "If moving in Y direction, ask tiles in y+1 if there are any 'Things' in the space"

4 hours ago, straight_stewie said:

Many standard library collection types are just abstractions over C style arrays, and rely on bare pointers for efficiency sake. Using them safely is possible under abstractions.

I'm working with things "dangerously" if I can learn how to use them. I've already spent a few times looking for bugs by attempting to access elements of an array out of bounds by inserting incorrect variables to the indices.

Though your statement to "use them safely" seems to say that I'm being a bit nieve in my reply.

4 hours ago, straight_stewie said:

In C#, we can define multidimensional arrays as type[firstIndexSize, secondIndexSize]. For std::array we would define it as std::array<std::array<type, secondIndexSize>, firstIndexSize>. That is, it is a std::array with length firstIndexSize that contains std::arrays with length secondIndexSize that contains type.

Yes. Okay, that makes more sense. How would I access specific indicies then?

I'm used to "arr[x][y]" (or arr[x,y] for C#)

My current implementation of the 2D array leaves the values as blank until I assemble it in a function that assembles the data in the "MapCreate()" function by accessing indicies directly.

4 hours ago, straight_stewie said:

Use include guards instead:



// File foo.h
#ifndef Bar_H
#include "bar.h"
#define Bar_H
#endif
  
class foo
{
private:
  *bar _someBar;
}

// File bar.h
#ifndef Foo_H
#include "foo.h"
#define Foo_H
#endif
  
class bar
{
private:
  *foo _someFoo;
}

Ahh okay. I had #pragma once from Visual Studio already. That #ifndef is definitely more up my alley.

4 hours ago, straight_stewie said:

Get a copy of TC++PL: The C++ Programming Language, by Bjarne Stroustrup.

Will do!

4 hours ago, straight_stewie said:

Additionally, here is a quick introduction page: https://docs.microsoft.com/en-us/cpp/cpp/smart-pointers-modern-cpp?view=vs-2019

Use that page to get ideas of more specific things to look up. The most common types of smart pointers are unique_ptr and shared_ptr.

I'm seeing in the unique_ptr section they only let one pointer point to a pointed object (tough phrasing)

It seems to be the type of pointer I am looking for as I want a Thing to only be seen as "in" one Tile at a time. If I'm understanding it correctly, it can "hijack" an object from another pointer automatically making the first one a "nullptr" and assigning the pointed to object to the new one that hijacked the object.

Is that how it works?
It would "save" me the logic of clearing the previous pointer.
I'm working on a game so, when things die, all I have to do is destroy the object & that should clear the pointer for me it seems?


Unimportant

 
 
 
 
 
3
Spoiler
2 hours ago, Unimportant said:

@fpo Way too much to answer from my phone but 2 red flags that stick out immediately: 

Yeah it's a lot & supposedly intermediate->advanced subjects.

2 hours ago, Unimportant said:
  • Handing out pointers to things stored in collections like a std::vector and storing them is a recepy for disaster. std::vector will eventually relocate and invalidate all those pointers.

Yeah, I was going to write the logic to clear the pointers on their destruction.

This is probably why people were telling me to use smart pointers.

2 hours ago, Unimportant said:
  • 
    
    things[0].RequestMove((Tile**)&map[0][0], 10, 10); 

is plain wrong. &map[0][0] is a *Tile, not a **Tile. Casting it to make the compiler shut up doesnt change that. This is undefined behavior.

This line of code came from someone that handed me a LMGTFY link when I asked how I should use Forward Declaration. They told me I "should be able to just plop it in as "map" so, I doubt he's much more experienced than I am.

 

Plop it in as map so it'd look like so:


things[0].RequestMove(map, 10, 10); 

I tried that and was getting the errors from including documents that include itself so idk.

 

Thank you all very much for helping me understand this stuff.

Sometimes asking for help overburdens me with such poor explanations and extreme info dumps that I have to take a nap haha. Y'all are quite good at explaining things nicely.

Link to comment
Share on other sites

Link to post
Share on other sites

32 minutes ago, fpo said:

Though your statement to "use them safely" seems to say that I'm being a bit nieve in my reply.

 

By "use them under abstraction" I just mean that the arrays are private members of whatever classes, and are never publicly exposed. That is, the C style arrays are protected by your classes RAII pattern. You can use member functions to work with them, or friend functions if you prefer the container/algorithm pattern.

However, sometimes you can't help but expose the underlying array. If, for example, you decide you want to use GPU compute to help with your collision detection (I think that's close to what you said) then you don't have a choice but to use a bare array and expose it so that the GPU can consume it.

 

32 minutes ago, fpo said:

Yes. Okay, that makes more sense. How would I access specific indicies then?

I'm used to "arr[x][y]" (or arr[x,y] for C#)

The notation array[outerIndex][innerIndex] works fine for accessing it. The statement is parsed from left to right, and because the first part, array[outerIndex] returns an array, the second subscript operator is parsed as innerArray[innerIndex]. I should have been clearer that you do not have to specify the inner arrays size at the creation of the outer array. So something like std::array<std::array<int>, outerArraySize> compiles and works. Then you can add inner arrays, of different sizes even, in a loop.

 

32 minutes ago, fpo said:

I had #pragma once

For some reason I have trouble with #pragma once on MSVC as well. I don't know why. I just stick to Googles rule on the matter which just specifies a pattern for naming include guards, and also says that you should prefer include guards to forward declarations. There are various patterns to include guards. I prefer the one I showed but you might prefer another. Googling for a while should help you find one that you think works well.

 

32 minutes ago, fpo said:

I'm seeing in the unique_ptr section they only let one pointer point to a pointed object (tough phrasing)

It seems to be the type of pointer I am looking for as I want a Thing to only be seen as "in" one Tile at a time. If I'm understanding it correctly, it can "hijack" an object from another pointer automatically making the first one a "nullptr" and assigning the pointed to object to the new one that hijacked the object.

Is that how it works?
It would "save" me the logic of clearing the previous pointer.
I'm working on a game so, when things die, all I have to do is destroy the object & that should clear the pointer for me it seems?

I'm not gonna lie. We are stretching my knowledge on smart pointers quite a bit. I prefer to write code that minimizes the use of new and delete, naked or not. Like yourself, I'm still learning C++, but unlike yourself, I haven't gotten to a project where I would see a huge performance benefit from working with pointers yet.

From my admittedly limited understanding, unique_ptr is to be used where the lifetime of a pointer is the responsibility of a single object (or scope), and shared_ptr is to be used where the lifetime of a pointer is the responsibility of multiple objects (or scopes). The simple explanations that I've seen just say that the smart pointers automatically handle their actual pointers when they go out of scope, but I'm sure that there are more considerations than these. That's about all that I know about them right now. I apologize that I can't be of more help there.

ENCRYPTION IS NOT A CRIME

Link to comment
Share on other sites

Link to post
Share on other sites

@fpo Back behind a real keyboard, here goes...

 

1: Forward declaring

First, understand the problem.

When your project is compiled, only the cpp files are entry points for the compiler. Each cpp file is compiled separately, without knowledge of the other cpp files. The contents of included files are copied verbatim into the position of the include statement.

Thus, if we have some files:

  • Thing.h
#ifndef __THING_H__  //These 2 lines are called...
#define __THING_H__  //...include guard
  
#include "Tile.h"
  
class Thing
{
	Tile mTile; //Thing contains a Tile instance.
};

#endif //__THING_H__
  • Tile.h
#ifndef __TILE_H__
#define __TILE_H__  //Include guard...
  
#include "Thing.h"
  
class Tile
{
	Thing mThing; //Tile contains a Thing instance...  
};

#endif //__TILE_H__
  • Main.cpp
#include "Thing.h"
  
int main()
{
	Thing aThing; //Create Thing instance...
  
  	return 0;
}

The entry point for compilation is Main.cpp, and the preprocessor will expand this into:

class Tile
{
	Thing mThing; //Tile contains a Thing instance...  
};

class Thing
{
	Tile mTile; //Thing contains a Tile instance.
};
  
int main()
{
	Thing aThing; //Create Thing instance...
  
  	return 0;
}

This is literally what the compiler will see after the preprocessor is done. Note that:

  • If there were no include guards, this would've led to endless recursion. Thing.h will include Tile.h which includes Thing.h which includes Tile.h and so on forever.
  • class Thing can see the definition of class Tile but class Tile can't see the definition of class Thing. Thus this will fail to compile as it's impossible for Tile to have a instance of unknown class Thing.

Forward declaring can help solve this problem because it allows introducing a new name for a class to be defined later. However this means there are limitations to what you can do with a forward declared class:

class Thing;  //Forward declaration...

class Tile
{
	Thing mThing;   //Illegal, you can't have a instance of a forward declared class, compiler can't possibly know storage requirements. 
	Thing* mpThing; //Ok, pointers to forward declared classes are fine. Pointers have a fixed size so compiler knows storage requirements.
	Thing& mrThing; //Ok, references are pointers under the hood.
};

Thing
SomeFunction(Thing aThing); //Ok, function declaration which uses forward declared type as function parameter and/or return type.

Thing
AnotherFunction(Thing aThing)
{}	//Illegal, function definition with forward declared types as parameter and/or return type not allowed.

Thing&
YetAnotherFunction(Thing* apThing)
{}  //Ok, function definition with pointers or references to forward declared type as parameter and/or return type allowed, but without 
    //using it's members.

Typically, one would forward declare a required class in the header and include the full definition in the code file, where there are no conflicts:

 

Thing.h

#ifndef __THING_H__
#define __THING_H__

class Tile; //Forward declare Tile...

class Thing
{
public:

	Thing(Tile* aTile);  
  
private:
  
	Tile* mpTile; //Hold a pointer, which is allowed...
};

#endif //__THING_H__

Thing.cpp

#include "Thing.h"
#include "Tile.h" //You can include the full definition of Tile here without problem...

Thing::Thing(Tile* aTile) :
	mpTile(aTile)
{}

2: Smart Pointers

The purpose of smart pointers is to act as a resource handle for memory. Whenever memory is allocated dynamically it has to be freed at some point. By tying the allocation and freeing of memory to a automatic object on the stack this is handled automatically. This is a implementation of RAII - Resource acquisition is initialization. 

//Dumb pointer - bad code, don't do this!
void
DoSomething(Thing* apThing)
{
	if (/*something*/)
	{
		throw SomeException;  
	}
	//use apThing...  
}

void
DoSomethingElse(Thing* apThing)
{
	//use apThing...
	delete apThing; //Take ownership and delete the memory...
}

void
SomeFunction()
{
	Thing* pThing = new Thing; //Manual "old style" allocation. 
  
  	DoSomething(pThing); //If function DoSomething throws an exception, the delete pThing line will never be reached and memory is leaked.
  
  	DoSomethingElse(pThing); //Code poorly documents itself. Function DoSomethingElse actually takes over ownership of pointer and deletes it.
  
  	pThing->SomeMember(); //Calling member function on deleted Thing :(
  
  	delete pThing; //Deleting already deleted memory again is undefined behavior.
}

//Smart pointer
void
DoSomething(Thing* apThing)
{
	if (/*something*/)
	{
		throw SomeException;  
	}
	//use apThing...  
}
 
void
DoSomethingElse(std::unique_ptr<Thing> apThing)
{
	//use apThing...
  	//unique_ptr deletes managed object automatically when it goes out of scope.
}

void
SomeFunction()
{
	auto pThing = std::make_unique<Thing>();
  
  	DoSomething(pThing.get());  //Function DoSomething takes a plain dumb pointer to a Thing. This makes it clear it only wants to use
				//the pointer and does not take ownership. If the function throws an exception pThing will automatically be
				//deleted when it goes out of scope.
  
  	DoSomethingElse(pThing);	//Compile error. Calling by value would copy pThing, after which there would be 2 owners for the same memory.
					//std::unique_ptr is truly unique and cannot be copied, which is exactly what you want here.
  
  	DoSomethingElse(std::move(pThing)); //Ok, we move pThing, thereby explicitly handing over ownership to function DoSomethingElse.
 
  	//No need to manually delete.
}  

Note that plain dumb pointers are still fine in order to refer to some object but without taking responsibility/ownership. Stl containers like std::vector are another example of a resource handle.

 

3: (2D) arrays

One of the reasons we don't really like C style arrays in C++ is because they carry too little information by themselves to be really useful and if one has to manually supply that information you open up the way for errors. For example:

void
PopulateArray(int* array, int size)
{
  //...
}

void
PrintArray(int* array, int size)
{
  //... 
}

int
main()
{
  	auto constexpr size = 100;
	int myArray[size];
  
  	PopulateArray(myArray, size); //Manually passing along size each time is cumbersome... 
  	PrintArray(myArray, 101); //Uh oh! 
 
	return 0;  
}

An array name decays into a pointer that, after being passed to a function, has no information about the size of the array. We'd have to pass that information along manually, which is cumbersome and error prone. If we'd have used a std::vector instead, for example, all such problems go away, because std::vector knows it's own size.

 

2D arrays are even more problematic. Tile** does not mean "Pointer to 2D array of Tile" like most beginners seem to think. It actually means "Array of pointers, each pointing to an array of pointers to Tile", and no-one is guaranteeing it's even square, or that it's even a continuous block of memory for that matter.

 

Worse still, if you ever need to pass such a thing to a function that needs to modify the pointer itself you become a 3-star programmer (Tile***). Any serious programmer will simply stop looking at your code at that point.

 

One of the powers of C++ is that it allows building your own abstractions to hide the complexity. A 2D array can be stored in a plain 1D array of rows * columns size. Write yourself a 2D array class that handles all this complexity for you:

template <class T, int rows, int cols>
class Array2D
{
public:
  
	const T&
	Get(int row, int col) const
	{
		return mArray[row * cols + col];  
	}
  
	T&
	Get(int row, int col)
	{
		return mArray[row * cols + col];  
	}
  
private:
  
	std::array<T, rows * cols> mArray;
};

Which could be used as such:

Array2D<int, 5, 10> array;	//2D int array 5 rows, 10 columns
array.Get(1, 6) = 101;		//Assign 101 to item at row 1, column 6	
std::cout << array.Get(1, 6);	//Print item at row 1, column 6

 

Link to comment
Share on other sites

Link to post
Share on other sites

7 hours ago, straight_stewie said:

A std::vector and a std::array is significantly slower than a C style array for many applications, and this is especially apparent when speed matters, i.e. in graphics.

std::vector and std::array are just as fast as plain C style arrays. Why would they be any slower? All the abstractions get removed by the compiler until what is left is the plain memory, just like a C style array. If not, you're doing something wrong. Play around with Godbolt's compiler explorer to convince yourself.

Unless you ask std::vector or std::array for extra functionality, such as range checking or dynamically allocating memory. Things a plain C array can't even perform, then yes - you pay for that extra functionality.

Link to comment
Share on other sites

Link to post
Share on other sites

21 minutes ago, Unimportant said:

std::vector and std::array are just as fast as plain C style arrays. Why would they be any slower?

What follows is obviously not a perfect test, possibly not even a valid test. It's just something super quick. But on my machine the std::vector is always between 0.3 and 0.5 seconds slower:

#include <vector>
#include <iostream>
#include <chrono>

int main()
{
	int testSize = 10'000'000;
	std::vector<int> vectorTest = std::vector<int>(testSize);
	int* arrayTest = new int[testSize];

	for (int i = 0; i < testSize; i++)
	{
		vectorTest[i] = i;
		arrayTest[i] = i;
	}

	// Sum all of the values. We don't care if the result is accurate.
	auto arrayStart = std::chrono::high_resolution_clock::now();
	int sum = 0;
	for (int i = 0; i < testSize; i++)
		sum += arrayTest[i];

	auto arrayStop = std::chrono::high_resolution_clock::now();;
	auto arrayTime = std::chrono::high_resolution_clock::duration(arrayStop - arrayStart);


	auto vectorStart = std::chrono::high_resolution_clock::now();
	sum = 0;
	for (int i = 0; i < testSize; i++)
		sum += vectorTest[i];

	auto vectorStop = std::chrono::high_resolution_clock::now();
	auto vectorTime = std::chrono::high_resolution_clock::duration(vectorStop - vectorStart);



	std::cout << "Vector:\t" << vectorTime.count() << std::endl;
	std::cout << "Array:\t" << arrayTime.count() << std::endl;
}


Doing the array summing first does not seem to affect the results.

ENCRYPTION IS NOT A CRIME

Link to comment
Share on other sites

Link to post
Share on other sites

6 hours ago, fpo said:

That makes sense for a coding convention. Is my way incorrect? I like verbose, but if that's the way it's done, I can do it that way too.

The "problem" with the verbose way is that it depends on the definition of what you're assigning to it. Since it's a C style pointer the correct verbose check would be comparing it to 0.

7 hours ago, fpo said:

How would you "clear" a pointer's value without emptying the memory of the previously pointed to object?

You make a new pointer. "Clearing" pointers without using a call to free is asking for a memory leak.

7 hours ago, fpo said:

How do I access a specific index then with std::array?
In C arrays it's easy.

arr[x][y] like a 2D graph.

It's exactly the same.

7 hours ago, fpo said:

I meant, like how do I pass a C style array to a function by reference, so I'm operating on the array directly & not a copy of the array.

You can just pass the pointer.

Don't ask to ask, just ask... please 🤨

sudo chmod -R 000 /*

Link to comment
Share on other sites

Link to post
Share on other sites

A newbie here.

If the goal of the project is to learn pointers and memory management, then use them, otherwise I would go with STL. A minimal performance hit is totally worth it, it's much better than a memory leak. If you don't use it you will end up implementing your own vector class sooner or later, but why would you do that when smart people already done it?

Quote

I don't like the computer doing things for me. One of the reasons I am using C++. I want to have the control not apparent in other languages. It's okay if I break the computer, but I want to know why.

The name of the game is how not to break your own code.

ಠ_ಠ

Link to comment
Share on other sites

Link to post
Share on other sites

8 hours ago, straight_stewie said:

What follows is obviously not a perfect test, possibly not even a valid test. It's just something super quick. But on my machine the std::vector is always between 0.3 and 0.5 seconds slower:


#include <vector>
#include <iostream>
#include <chrono>

int main()
{
	int testSize = 10'000'000;
	std::vector<int> vectorTest = std::vector<int>(testSize);
	int* arrayTest = new int[testSize];

	for (int i = 0; i < testSize; i++)
	{
		vectorTest[i] = i;
		arrayTest[i] = i;
	}

	// Sum all of the values. We don't care if the result is accurate.
	auto arrayStart = std::chrono::high_resolution_clock::now();
	int sum = 0;
	for (int i = 0; i < testSize; i++)
		sum += arrayTest[i];

	auto arrayStop = std::chrono::high_resolution_clock::now();;
	auto arrayTime = std::chrono::high_resolution_clock::duration(arrayStop - arrayStart);


	auto vectorStart = std::chrono::high_resolution_clock::now();
	sum = 0;
	for (int i = 0; i < testSize; i++)
		sum += vectorTest[i];

	auto vectorStop = std::chrono::high_resolution_clock::now();
	auto vectorTime = std::chrono::high_resolution_clock::duration(vectorStop - vectorStart);



	std::cout << "Vector:\t" << vectorTime.count() << std::endl;
	std::cout << "Array:\t" << arrayTime.count() << std::endl;
}


Doing the array summing first does not seem to affect the results.

You seem to be compiling with no optimizations ?

 

Compiling without optimizations in gcc:

g++ -std=c++1z ltt_test.cpp

Indeed, vector is slower. Which is expected for un-optimized code:

Vector:	64576298
Array:	46003023

However, once you enable optimizations:

g++ -std=c++1z -O2 ltt_test.cpp

We get:

Vector:	49
Array:	340

Godbolt's compiler explorer shows that the compiler simply throws out the loops completely. Either because it is smart enough to pre-compute the result or because your code invokes undefined behavior (signed int overflow is undefined), so it's a broken program anyway and the compiler can have it's way with it.

Link to comment
Share on other sites

Link to post
Share on other sites

Straight Stewie first reply

Spoiler
Spoiler
On 4/30/2020 at 11:28 PM, straight_stewie said:
 

By "use them under abstraction" I just mean that the arrays are private members of whatever classes, and are never publicly exposed. That is, the C style arrays are protected by your classes RAII pattern. You can use member functions to work with them, or friend functions if you prefer the container/algorithm pattern.

 

Ahh okay. I don't know that I would have created abstraction, but I understand. I thought you meant I had to use a pre-existing protection abstraction or I would be daft.

Quote


However, sometimes you can't help but expose the underlying array. If, for example, you decide you want to use GPU compute to help with your collision detection (I think that's close to what you said) then you don't have a choice but to use a bare array and expose it so that the GPU can consume it.

In a way. I am not sending the data to the GPU, but, but physics is calculated once every 1/60/second. (Target 60 FPS physics calculations.)

Quote

The notation array[outerIndex][innerIndex] works fine for accessing it. The statement is parsed from left to right, and because the first part, array[outerIndex] returns an array, the second subscript operator is parsed as innerArray[innerIndex]. I should have been clearer that you do not have to specify the inner arrays size at the creation of the outer array. So something like std::array<std::array<int>, outerArraySize> compiles and works. Then you can add inner arrays, of different sizes even, in a loop.

That's easy enough. A bit difficult at first glance with the swapped indices from creation to use, but nothing a quick comment can't re-assure.

How would you pass that by reference to a function then? (Function implementation, use of array, & function call)

tbh since somewhere in this thread it was specified that std::vectors & std::arrays I might just use vectors.

I don't plan on creating a new array every time, but it could be useful (if not memory fragment breaking) to be able to resize the dynamic arrays based on the size of the map being loaded.

Quote

For some reason I have trouble with #pragma once on MSVC as well. I don't know why. I just stick to Googles rule on the matter which just specifies a pattern for naming include guards, and also says that you should prefer include guards to forward declarations. There are various patterns to include guards. I prefer the one I showed but you might prefer another. Googling for a while should help you find one that you think works well.

I might check out some more later but I will stick to the #ifndef for now just so I can get back to developing. I meant to have this physics system & an animation engine completed like 3 days ago haha.

Quote

I'm not gonna lie. We are stretching my knowledge on smart pointers quite a bit. I prefer to write code that minimizes the use of new and delete, naked or not. Like yourself, I'm still learning C++, but unlike yourself, I haven't gotten to a project where I would see a huge performance benefit from working with pointers yet.

 

Quote


From my admittedly limited understanding, unique_ptr is to be used where the lifetime of a pointer is the responsibility of a single object (or scope), and shared_ptr is to be used where the lifetime of a pointer is the responsibility of multiple objects (or scopes). The simple explanations that I've seen just say that the smart pointers automatically handle their actual pointers when they go out of scope, but I'm sure that there are more considerations than these. That's about all that I know about them right now. I apologize that I can't be of more help there.

 

No problem! I think Unimportant is sharing some decent information on the subject. Public threads are available for everyone to learn!

 

Unimportant first reply

Spoiler
Spoiler
On 5/1/2020 at 1:41 AM, Unimportant said:

1: Forward declaring

First, understand the problem.

When your project is compiled, only the cpp files are entry points for the compiler. Each cpp file is compiled separately, without knowledge of the other cpp files. The contents of included files are copied verbatim into the position of the include statement.

Thus, if we have some files:

~~snip~~

This is literally what the compiler will see after the preprocessor is done. Note that:

  • If there were no include guards, this would've led to endless recursion. Thing.h will include Tile.h which includes Thing.h which includes Tile.h and so on forever.
  • class Thing can see the definition of class Tile but class Tile can't see the definition of class Thing. Thus this will fail to compile as it's impossible for Tile to have a instance of unknown class Thing.

Forward declaring can help solve this problem because it allows introducing a new name for a class to be defined later. However this means there are limitations to what you can do with a forward declared class:

~~code snip~~

Typically, one would forward declare a required class in the header and include the full definition in the code file, where there are no conflicts:

~~snip moar code~~

 

That makes sense. I'm guessing I forward declared incorrectly by using struct instead of class.

Can you call functions based on a pointer?
IE

forward declare a class,

hold a pointer to a class not included in the header file

in CPP call a function part of the pointed to object's class.

 

I'm guessing the CPP would require an include to the header of the class that was forward declared.

 

Quote

2: Smart Pointers

The purpose of smart pointers is to act as a resource handle for memory. Whenever memory is allocated dynamically it has to be freed at some point. By tying the allocation and freeing of memory to a automatic object on the stack this is handled automatically. This is a implementation of RAII - Resource acquisition is initialization. 



//Dumb pointer - bad code, don't do this!
void
DoSomething(Thing* apThing)
{
	if (/*something*/)
	{
		throw SomeException;  
	}
	//use apThing...  
}

void
DoSomethingElse(Thing* apThing)
{
	//use apThing...
	delete apThing; //Take ownership and delete the memory...
}

void
SomeFunction()
{
	Thing* pThing = new Thing; //Manual "old style" allocation. 
  
  	DoSomething(pThing); //If function DoSomething throws an exception, the delete pThing line will never be reached and memory is leaked.
  
  	DoSomethingElse(pThing); //Code poorly documents itself. Function DoSomethingElse actually takes over ownership of pointer and deletes it.
  
  	pThing->SomeMember(); //Calling member function on deleted Thing :(
  
  	delete pThing; //Deleting already deleted memory again is undefined behavior.
}

//Smart pointer
void
DoSomething(Thing* apThing)
{
	if (/*something*/)
	{
		throw SomeException;  
	}
	//use apThing...  
}
 
void
DoSomethingElse(std::unique_ptr<Thing> apThing)
{
	//use apThing...
  	//unique_ptr deletes managed object automatically when it goes out of scope.
}

void
SomeFunction()
{
	auto pThing = std::make_unique<Thing>();
  
  	DoSomething(pThing.get());  //Function DoSomething takes a plain dumb pointer to a Thing. This makes it clear it only wants to use
				//the pointer and does not take ownership. If the function throws an exception pThing will automatically be
				//deleted when it goes out of scope.
  
  	DoSomethingElse(pThing);	//Compile error. Calling by value would copy pThing, after which there would be 2 owners for the same memory.
					//std::unique_ptr is truly unique and cannot be copied, which is exactly what you want here.
  
  	DoSomethingElse(std::move(pThing)); //Ok, we move pThing, thereby explicitly handing over ownership to function DoSomethingElse.
 
  	//No need to manually delete.
}  

Note that plain dumb pointers are still fine in order to refer to some object but without taking responsibility/ownership. Stl containers like std::vector are another example of a resource handle.

Wouldn't a function containing a "dumb" pointer have an issue if an exception was thrown? Don't you have to "delete" it to clear its use?
 

In the above section, you said if "DoSomething" throws an exception, "pThing will automatically be deleted." Would this cause an error if you tried to use "pThing" after the exception is thrown? 
That move function is definitely useful.

How do you make a smart pointer hold no value? IE "I am currently not pointing to anything" without completely destroying the pointer. Like how you can set a variable to "null" in some languages with the variable still existing & being able to be read, and written to.

How do you create a smart pointer without giving it a value? Or is that impossible with the RAII? I want all the "Tile" objects to point to nothing until a "Thing" tells a "Tile" that the "Tile" can point to the current "Thing" and then when a Thing leaves the Tile or is gameplay destroyed (like killing an enemy) to have Tile point to nothing.

Quote

 

3: (2D) arrays

One of the reasons we don't really like C style arrays in C++ is because they carry too little information by themselves to be really useful and if one has to manually supply that information you open up the way for errors. For example:

~~snip C array accessed out of bounds~~

I've had that bug a few times & found it through the debugger by reading the value of the variables indicating which index to access.

Quote

An array name decays into a pointer that, after being passed to a function, has no information about the size of the array. We'd have to pass that information along manually, which is cumbersome and error prone. If we'd have used a std::vector instead, for example, all such problems go away, because std::vector knows it's own size.

 

2D arrays are even more problematic. Tile** does not mean "Pointer to 2D array of Tile" like most beginners seem to think. It actually means "Array of pointers, each pointing to an array of pointers to Tile", and no-one is guaranteeing it's even square, or that it's even a continuous block of memory for that matter.

 

Worse still, if you ever need to pass such a thing to a function that needs to modify the pointer itself you become a 3-star programmer (Tile***). Any serious programmer will simply stop looking at your code at that point.

 

One of the powers of C++ is that it allows building your own abstractions to hide the complexity. A 2D array can be stored in a plain 1D array of rows * columns size. Write yourself a 2D array class that handles all this complexity for you:

~~snip custom abstraction of 2D array~~

Which could be used as such:



Array2D<int, 5, 10> array;	//2D int array 5 rows, 10 columns
array.Get(1, 6) = 101;		//Assign 101 to item at row 1, column 6	
std::cout << array.Get(1, 6);	//Print item at row 1, column 6

 

How do you pass a normal array to a function by reference? If doing the custom abstraction ends up being a method I use. Many online indicate using 1Dimensional arrays to be the "only" way to pass arrays to a function by reference or value.
How do you pass a vector or array to a function by reference? Same way as an integer variable?

How do you access specific indicies of a vector passed to a function by reference?

 

Straight Stewie recommended the possibility of a 2D array by making an array contain arrays of type.


int size = 10;
int otherSize = 15;
std::array<std::array<Thing, size>, otherSize>

If I do this method (provided it's a good method to follow) how would I pass it to a function & how would I access specific indices in the function?

Sorry for a ton of questions. I haven't had to work in this type of workflow before, so I'm completely not used to it. I could just plop my C array into my globals.h but the moment I have to work with anyone more experienced in C++, I would be in way over my head.

 

Sauron's first reply

 

Spoiler
Spoiler
On 5/1/2020 at 6:05 AM, Sauron said:

The "problem" with the verbose way is that it depends on the definition of what you're assigning to it. Since it's a C style pointer the correct verbose check would be comparing it to 0.

Ahh okay. Archaic, but makes sense.

Quote

You make a new pointer. "Clearing" pointers without using a call to free is asking for a memory leak.

I would still manage the pointed to object another way, I just want classes to be able to point to specific instances of another class without having to compute a lookup/search every time I need to access a specific instance.

Quote

You can just pass the pointer.

Do you have an example? I don't understand how it's done.

 

shadow_ray

Spoiler
Spoiler
On 5/1/2020 at 6:15 AM, shadow_ray said:

A newbie here.

If the goal of the project is to learn pointers and memory management, then use them, otherwise I would go with STL. A minimal performance hit is totally worth it, it's much better than a memory leak. If you don't use it you will end up implementing your own vector class sooner or later, but why would you do that when smart people already done it?

The goal of the project is to practice C++ & make a completed game in a custom game engine.
Big resume piece so I can get a job in making game engines with C++.

Quote

The name of the game is how not to break your own code.

That's why I want to know why/how I broke the computer if I break things.

"learn by failing"

tbh, I still wouldn't know what the debugger actually did if I didn't use C style arrays & have bugs by accessing indicies out of bounds or by inputting unacceptable indicies.

 

 

 

 

 

 

Link to comment
Share on other sites

Link to post
Share on other sites

23 minutes ago, fpo said:

I would still manage the pointed to object another way, I just want classes to be able to point to specific instances of another class without having to compute a lookup/search every time I need to access a specific instance.

That's fine but there are cleaner way to do this than making a dangling pointer - you could point it to a new empty object or simply to the next thing it needs to point to.

24 minutes ago, fpo said:

Do you have an example? I don't understand how it's done.

void doTheThing(int **myarray){
	myarray[0][0] = 0;
}

more here.

 

By the way I don't know what you're dong with the quotes but this is how I see your replies:

Spoiler

image.png.11860f0d9a0f436df770a1c1ecc32437.png

 

Don't ask to ask, just ask... please 🤨

sudo chmod -R 000 /*

Link to comment
Share on other sites

Link to post
Share on other sites

59 minutes ago, fpo said:

I'm guessing I forward declared incorrectly by using struct instead of class.

No, in C++ a struct and a class are the same, except for the fact that struct members and inheritance are public by default whereas they are private by default for class.

 

1 hour ago, fpo said:

Can you call functions based on a pointer?
IE

forward declare a class,

hold a pointer to a class not included in the header file

in CPP call a function part of the pointed to object's class.

 

I'm guessing the CPP would require an include to the header of the class that was forward declared.

 

Correct. As per the given example you can include the header of the forward declared class in the CPP file and then you can use it completely.

 

1 hour ago, fpo said:

Wouldn't a function containing a "dumb" pointer have an issue if an exception was thrown? Don't you have to "delete" it to clear its use?

The dumb pointer simply refers to a object that is owned elsewhere, so it can be accessed, but the owner is responsible for it's deletion. In the given smart pointer example the memory is owned by std::unique_ptr pThing in function SomeFunction. A dumb pointer to the managed object is passed to function DoSomething so that DoSomething can access the object, but the ownership and responsibility remains with pThing in SomeFunction. If DoSomething throws then the stack will be unwound until someone tries to catch the exception. It's not being caught in DoSomething so that function will exit. It's not being caught in SomeFunction either, so that function will exit as well, but because pThing is a automatic variable in SomeFunction it will go out of scope once that function exits and it's destructor will be called, which will delete the managed memory.

 

1 hour ago, fpo said:

In the above section, you said if "DoSomething" throws an exception, "pThing will automatically be deleted." Would this cause an error if you tried to use "pThing" after the exception is thrown? 

You can't even do that. If DoSomething throws the stack will be unwound until the exception is caught. SomeFunction will exit immediately and execution will resume at some lower level where the exception is caught.

 

1 hour ago, fpo said:

How do you make a smart pointer hold no value? IE "I am currently not pointing to anything" without completely destroying the pointer. Like how you can set a variable to "null" in some languages with the variable still existing & being able to be read, and written to.

How do you create a smart pointer without giving it a value? Or is that impossible with the RAII? I want all the "Tile" objects to point to nothing until a "Thing" tells a "Tile" that the "Tile" can point to the current "Thing" and then when a Thing leaves the Tile or is gameplay destroyed (like killing an enemy) to have Tile point to nothing.

A default constructed std::unique_ptr is empty. You can manually release it and it has a overload for operator bool to check if it's currently managing something:

std::unique_ptr<Thing> pThing;  //default constructed unique_ptr contains nullptr by default

if (pThing)  //operator bool
{
  //will not execute, pThing contains nullptr
}

pThing.reset(new Thing);  //Create new Thing and have pThing manage it (don't do this, use std::make_unique)

if (pThing)  //operator bool
{
  //will execute, pThing does not contain nullptr
}

pThing.release(); //Delete object under management, pThing free again.

HOWEVER The point of the story was that smart pointers are only to be used when you need to manage dynamically allocated memory. In your case that doesn't seem to be the case. If your array of Tiles has a size that is known at compile time, just make it a std::array (or your own Array2D that uses std::array under the hood). If the size is unknown at compile time then use std::vector (or your own Vector2D that uses std::vector under the hood).

 

1 hour ago, fpo said:

How do you pass a normal array to a function by reference?

A C style array decays to a pointer:

void
UseIntArray(int* array, int size)
{
	for (int i = 0; i < size; ++i)
	{
		array[i] = //do something...   
	}
}

int 
main()
{
	int array[10];
	UseIntArray(array, 10);
  
	return 0;  
}

 

1 hour ago, fpo said:

How do you pass a vector or array to a function by reference? Same way as an integer variable?

C++ actually supports references as a separate concept and those are preferred:

void
UseIntVector(std::vector<int>& vec)
{
	for (auto& elem : vec)
	{
		//Use elem   
	}
}

int
main()
{
	std::vector<int> vec;
	//populate vector...
	UseIntVector(vec);

	return 0;
}

But you could use pointers if you wanted to:

void
UseIntVector(std::vector<int>* pVec)
{
	for (auto& elem : *pVec)
	{
    	//Use elem
	}
}

int
main()
{
	std::vector<int> vec;
	//populate vector...
	UseIntVector(&vec);

	return 0;
}

A std::array is somewhat trickier as it's size is part of the template. So you'd either have to write your function to only accept std::arrays of fixed size or the function itself has to be a template:

template <std::size_t size>
void
UseIntArray(std::array<int, size>& array)
{
	for (auto& elem : array)
	{
		//Use elem...
	}
}

int
main()
{
  	std::array<int, 100> array;
  	UseIntArray(array); //Template deduction passes along size automatically.
  
	return 0;  
}

 

1 hour ago, fpo said:

Straight Stewie recommended the possibility of a 2D array by making an array contain arrays of type.


int size = 10;
int otherSize = 15;
std::array<std::array<Thing, size>, otherSize>

If I do this method (provided it's a good method to follow) how would I pass it to a function & how would I access specific indices in the function?

It's not recommended as it still is not guaranteed to be a continuous block of memory.

 

Many of your technical problems are artificial, stemming from poor overall design, so I'd focus my efforts there. For example, your "map" should probably be a class in it's own right, not a plain 2D array of Tiles. Then the "map" object manages it's implementation details internally and other objects ask the map to perform tasks. This keeps "map"'s implementation details restricted to the "map" class and stops it from being scattered all around the code as it is now.

 

In simple terms, The "Human" object asks a "Dog" object to walk. The "Human" object doesn't go and manipulate the dog's legs directly.

 

Link to comment
Share on other sites

Link to post
Share on other sites

4 hours ago, fpo said:

That's why I want to know why/how I broke the computer if I break things.

"learn by failing"

tbh, I still wouldn't know what the debugger actually did if I didn't use C style arrays & have bugs by accessing indicies out of bounds or by inputting unacceptable indicies.

Yeah sure..

I managed to make SFML work with vs19. It took me a while :D

 

index_out_of_range.png.4de8ae881c079d61285b26e30a75dbf5.png

ಠ_ಠ

Link to comment
Share on other sites

Link to post
Share on other sites

Thanks to everyone for the help!!

 

Sauron

Spoiler
Spoiler
On 5/1/2020 at 2:12 PM, Sauron said:

That's fine but there are cleaner way to do this than making a dangling pointer - you could point it to a new empty object or simply to the next thing it needs to point to.

Thanks! I think I need to do some structure re-design.

Quote

 

By the way I don't know what you're dong with the quotes but this is how I see your replies:

 

Yeah, that's because of Grammerly. No idea why it does that. It's an admin known issue. I try to fix them when I remember. Forgot for that reply. Has to be edited after posting.

 

Unimportant

Spoiler
Spoiler
On 5/1/2020 at 3:50 PM, Unimportant said:

The dumb pointer simply refers to a object that is owned elsewhere, so it can be accessed, but the owner is responsible for it's deletion. In the given smart pointer example the memory is owned by std::unique_ptr pThing in function SomeFunction. A dumb pointer to the managed object is passed to function DoSomething so that DoSomething can access the object, but the ownership and responsibility remains with pThing in SomeFunction. If DoSomething throws then the stack will be unwound until someone tries to catch the exception. It's not being caught in DoSomething so that function will exit. It's not being caught in SomeFunction either, so that function will exit as well, but because pThing is a automatic variable in SomeFunction it will go out of scope once that function exits and it's destructor will be called, which will delete the managed memory.

Side question... Will memory be freed by calling a destructor manually?

ie
 


main () {
  //Create thing
  Thing thing;
  thing.Thing();
  
  //Use thing
  thing.Method();
  
  //Done with thing. Intend to free memory. 
  // thing is no longer needed. 
  thing.~Thing();
  

}

 

Quote

A default constructed std::unique_ptr is empty. You can manually release it and it has a overload for operator bool to check if it's currently managing something:



std::unique_ptr<Thing> pThing;  //default constructed unique_ptr contains nullptr by default

if (pThing)  //operator bool
{
  //will not execute, pThing contains nullptr
}

pThing.reset(new Thing);  //Create new Thing and have pThing manage it (don't do this, use std::make_unique)

if (pThing)  //operator bool
{
  //will execute, pThing does not contain nullptr
}

pThing.release(); //Delete object under management, pThing free again.

HOWEVER The point of the story was that smart pointers are only to be used when you need to manage dynamically allocated memory. In your case that doesn't seem to be the case. If your array of Tiles has a size that is known at compile time,

Noting to your final statement.... I will be re-designing some of my code.

I will basically just hold references to objects.
IE
pointer to map & ask map to operate with certain basic variables.

Presuming all these

Quote

C++ actually supports references as a separate concept and those are preferred:

But you could use pointers if you wanted to:

In general, references are preferred or just for vectors/arrays?

I thought they were basically the same thing.

Quote

Many of your technical problems are artificial, stemming from poor overall design, so I'd focus my efforts there. For example, your "map" should probably be a class in it's own right, not a plain 2D array of Tiles. Then the "map" object manages it's implementation details internally and other objects ask the map to perform tasks. This keeps "map"'s implementation details restricted to the "map" class and stops it from being scattered all around the code as it is now.

 

In simple terms, The "Human" object asks a "Dog" object to walk. The "Human" object doesn't go and manipulate the dog's legs directly.

 

I think that's the real answer to my problem.
To quote IRobot:
"You have to ask the right questions." haha.

 

 

 

Shadow Ray

Spoiler
Spoiler
On 5/1/2020 at 5:57 PM, shadow_ray said:

Yeah sure..

I managed to make SFML work with vs19. It took me a while :D

Setting up API with C++ can be a bit tricky if you don't know how to do it.

 

 

Link to comment
Share on other sites

Link to post
Share on other sites

1 hour ago, fpo said:

Side question... Will memory be freed by calling a destructor manually?

ie
 


main () {
  //Create thing
  Thing thing;
  thing.Thing();
  
  //Use thing
  thing.Method();
  
  //Done with thing. Intend to free memory. 
  // thing is no longer needed. 
  thing.~Thing();
  

}

This isn't required (and actually won't compile). You're creating a automatic variable on the stack, not allocating dynamically. Its destructor will be called automatically when it goes out of scope and stack memory is managed automatically. Only dynamically allocated memory needs to be freed explicitly, but smart pointers and containers take care of that for you.

 

Calling the constructor manually on a already constructed instance should fail to compile.

Calling the destructor manually is allowed for special reasons but in general it's wrong as the destructor would be called again automatically when the object goes out of scope and destructing twice is undefined behavior.

 

1 hour ago, fpo said:

In general, references are preferred or just for vectors/arrays?

I thought they were basically the same thing.

When referring to another object owned elsewhere, references are preferred because of their syntax (references don't require special syntax as opposed to pointers). However, there are certain situations where you don't have a choice:

  • Pointers can be nullptr, references can't be null. Sometimes you want nullptr to be a possibility, saying "currently not pointing to anything". Sometimes you want to be sure something is always pointing to something valid, in that case use a reference.
  • Pointers can be repointed to point to something else, references can't be reseated. A side effect of this is the implicit deletion of assignment operators on objects which have a reference member. For this reason you'll often be forced to use pointers as classmembers.
Link to comment
Share on other sites

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×