C++ Questions

Pages: 1... 56789... 13
1
2
3
4
5
6
7
8
9
10
11
12
13
int main() {
	//for (int i = 0; i < 10; ++i)

	int i = 0;

l1:
	if (i < 10) {
		// Loop body goes here

		++i;
		goto l1;
	}
}


DON'T DO THIS!
This is just to get a better feel for what the processor is and is not capable of. It will be a while before I look at any assembly, but I will have that itch too. Knowing what the processor is doing and how it handles things is important at times in writing better code. Peter87 says that creating such things as ints/chars/simple structs might be "free" inside the loop and this is good to know, and it is beneficial to know as much as one can about the compiler inner workings. I know the simple basics, it creates the obj's and linker links them to create the executable..etc.

If I would have said that for() loop was treated like below, you would have told me no and that the "i" does not exist past the body. So it helps in that sense.

1
2
3
4
5
6
7
8
int i = 0;
{
	if (i < 10)
	{
		//for() body...do whatever
		++i;
	}
}



seeplus
Trust me it was on my mind, since I could not use a continue. I have read not to use it in another post on here somewhere and I know since then, thanks. Is it a remnant from C and can lead to problems in C++ and just better replaced with continue/break (when in a loop only) and alternative code.

Is it one of those things where you can use it on simple code, such as 1-5 lines, or NEVER EVER not even with a single line. I have tried it and it seems to work on my trials. What is the big deal with that, after all it's in the books?

Somewhere in there after the if() statement there is assembly language fairy dust that says go back to the top of the if() statement and run the check again.
Last edited on
Re goto. The seminal reference to not using goto is Dijkstra's paper 'Goto statement considered harmful' from 1968. See:
https://homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.pdf

The first time I read "Goto Considered Harmful" I was missing a lot of context.

Dijkstra criticized goto's abuse in unstructured programs. However, unstructured programs are ancient history, so I found it tough to understand the issues he described. This is because, for decades, structured programming has had support designed into silicon as well as language support from all programming languages. It's ubiquitous.

An unstructured program might be written in an imaginary version of C with no libraries, no structs, and no functions. No while loops, no for loops, and no do loops. The language would have if and probably switch, but most importantly, goto. Since there's no functions, the entire program has to go in one code block. If our programmer was undisciplined, our "unstructured" program would be spaghetti.

Try viewing Dijkstra's paper in the context of spaghetti.
Last edited on
It wasn’t that long ago that unstructured language constructs continued to exist.

When I was learning to program in the late 80’s (“not that long ago”, lol), I was playing with 3D graphics algorithms and backface culling. Being just a wee lad, I had no idea how it worked and was looking to learn by downloading and translating people’s algorithms into Pascal (or C).

I remember one program that made a pretty picture of a house, but I could not follow the code for the life of me. I have never seen, before or since — including in articles about spaghetti code — code that was so convoluted.

I tried to unwrap it for about a week, then gave up.

(It was in GW-BASIC, and used GOTO liberally.)
I remember one program that made a pretty picture of a house

https://legacy.cplusplus.com/forum/beginner/265417/#msg1143056
what the processor can do is a useful thing to know. They do a lot more than is theoretically strictly necessary and a lot less than you might think.

for the intel processors:
they branch (this includes loops) via jump statements (goto like statements) such as jump when this register is zero, or is not zero. There isnt really a loop statement at all.. all the c++ do/while/for loops become the dreaded goto statements when you get into the machine language :) The same statements are used for if/else etc and any other time the code needs to 'goto' or 'jump'.

so all this theorycrafting and what we have in c++ is an abstraction to let humans write loops a few different ways to easily express your ideas. The machine has no idea that you chose a do/while so that the loop body would fire at least one time or a for vs a while loop. The machine just jumps around as directed.
Last edited on
The hard thing to convince people is that the underlying hardware (and assembly language) are different constructs than, say, C++.

When programming in a HLL, you should think in terms of that language, and not necessarily in terms of what you imagine the hardware is doing underneath. That’s the job of compiler writers.

There is a difference, and forgetting it makes for bad code.
It wasn’t that long ago that unstructured language constructs continued to exist.

When I did stumble on LaTeX, its introduction stressed on how it supports structure in documents, unlike the "unstructured mess" of MS Word doc format. The MS product -- like programming languages -- may have evolved since, but that does not prevent users from avoiding the helpful features.
Last edited on
The book has these snippets of code and has a line that clears the errors of the stringstream, "ss.clear()". The only explanation about that line is this, "Then, the clear() function of the string stream object sets the stream to a good state."

I sure do see that it is needed, otherwise it does not work without clearing it and I have tried it manually too without any loops. After the first stringstream extraction operator, it no longer extracts the data "ss >> low >> high".

12) But why? What is the cause of the error in the first place, that it needs a clear? Is this something that just has to be done when working with stringstream extraction?


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
#include<iostream>
#include<fstream>
#include<string>
#include<iomanip>
#include<sstream>
using namespace std;

int main()
{
	string filename{ "temps.txt" };

	ofstream outFile(filename);
	if (outFile)
	{
		outFile << fixed << setprecision(1);
		outFile << 48.4 << " " << 57.23 << '\n';
		outFile << 46.0 << " " << 50.0 << '\n';
		outFile << 78.3 << " " << 101.4 << '\n';
		outFile.close();
	}
	
	ifstream inFile(filename);
	if (inFile)
	{
		string line;
		stringstream ss;
		double low, high;

		cout << fixed << setprecision(1);
		while (getline(inFile, line))
		{
			ss.str(line);
			ss.clear();

			if (ss >> low >> high)
				cout  << setw(8) << low << setw(8) << high << endl;
		}
		inFile.close();
	}
	else
		cout << "Could not open file \"" << filename << "\"!\n";

	return 0;
}
It’s not an error, it is hitting the end of the stringstream every time through the loop (or actually errs), which, like an error, also stops attempts to collect input.

Because the stringstream is reused by the loop it must have its state reset to “valid and ready to read” each time.

The stringstream is actually out-of-place. It should be declared inside the loop.

Let us rewrite that example using some more idiomatic patterns:

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
#include <fstream>   // I personally prefer to order these alphabetically.
#include <iomanip>   // This makes it so much easier to see whether a
#include <iostream>  // required header is listed
#include <sstream>
#include <string>

// `using namespace std;` is for wannabes and script kiddies. Break the habit!

int main()
{
	// Prefer natural syntax -- it compiles to the exact same thing
	std::string filename = "temps.txt";
	
	// Our file output is simple enough we don't even need to name
	// our temporary variable. It creates the file, and if successful,
	// writes data to it, then closes the file when the variable is
	// destroyed.
	std::ofstream(filename)
		<< std::fixed << std::setprecision(1)
		<< 48.4 << " " << 57.23 << '\n'
		<< 46.0 << " " << 50.0  << '\n'
		<< 78.3 << " " << 101.4 << '\n';
		
	// Now we need a named (== persistent) variable.
	// It'll be destroyed at the end of main(), which will also
	// close the file, so RAII has again comes to the rescue!
	std::ifstream f(filename);
	
	// Prefer to check errors first. This keeps the error response 
	// close to the line that may cause it, and therefore significantly
	// reduces mental load on you, the programmer. The logic reads:
	//
	//   try something
	//   --> wut? No work?! CRASH AND BURN!!!
	//
	//   do next thing, 'cause everything is hunky dory good here
	//
	if (!f)
	{
		// Print information about errors to standard error.
		std::cerr<< "Could not open file \"" << filename << "\"!\n";
		return 1;
	}
	
	std::cout << std::fixed << std::setprecision(1);
	
	// The only variable that needs to persist outside the `while` loop is `line`[*1*].
	std::string line;
	while (getline(f, line))
	{
		// All the other variable belong _inside_ the loop.
		// (Let the compiler decide how to make this efficient!)
		std::istringstream ss(line);  // an _input_ string stream
		double low, high;
		
		// Attempt to read a line and print the values we got
		if (ss >> low >> high)
			std::cout << std::setw(8) << low << std::setw(8) << high << "\n";
		
		// (Note that an error in obtaining values from the
		//  line does NOT stop attempts to read further lines!)[*2*]
	}
	
	// Here both `line` and `f` go out of scope and RAII cleans up.
	// Notice also that main() is a _special_ function: You do not need
	// to `return 0`!
}


*1*
It is worth noting that a while loop does not make it easy to scope the context of that line variable. You can do it though. The first way is just to add an extra pair of braces {,}:

1
2
3
4
5
6
7
	{
		std::string line;
		while (getline(f, line))
		{
			...
		}
	}

But simpler is just to switch to a for loop:

1
2
3
4
	for (std::string line;  getline(f, line);  )
	{
		...
	}

This should rarely be a concern, though. Just keep your variable declarations close to where you use them.


*2*
The next thing is to recognize that the loop does not terminate if the input line could not be parsed into two integers. That is the purpose of using the stringstream.

You can verify this by making a small modification to the file you create:

1
2
3
4
5
6
	std::ofstream(filename)
		<< std::fixed << std::setprecision(1)
		<< 48.4 << " " << 57.23 << '\n'
		<< 46.0 << " " << 50.0  << '\n'
		<< "boooo! bad data!\n"           // <-- added this for demonstration
		<< 78.3 << " " << 101.4 << '\n';

Observe that the program still functions!

If it is important to fail if any input is bad, that requires you to both notice (which the current code does not) and to propagate the error back to the file input stream f.

1
2
3
4
5
6
		ss >> low >> high;
		if (!ss)
		{
			f.setstate( std::ios::failbit );
			continue;
		}

For this simple loop, the same results can be accomplished by simply terminating the loop:

1
2
3
4
		ss >> low >> high;
		if (!ss) break;

		std::cout << std::setw(8) << low << ...

(Notice how we follow the same pattern: try something, check for error (and act if there is one), then continue on happily.


Finally, notice that we don’t spend a lot of time validating that file creation succeeded, etc. This is because file streams have that error/state flag, and unless it is clear then no file operations can occur, making attempts to do things with them effectively no-ops. This unclutters your code nicely!


Well, hope this little tutorial helps make sense of streams in C++. Good luck!
Last edited on
Duthomhas, thank you sir that was superb! Allow me to make some notes...

12A Duthomhas)
Someone had mentioned one time on this site that we should be using the NEW way to initialize variables. Although, honestly I do prefer the older way and sometimes when I see small print I have to ask myself is that a function or initialization. Older way is so much easier on the eyes for me, even in text within the IDE.

1
2
3
double dNum{0.0};
string filename{ "temps.txt" };
std::string filename = "temps.txt";



12B Duthomhas) So the short answer is that is that we "must have its state reset to “valid and ready to read” each time." and that there are workaround to this (such as declaring the stringstream within the leap, so that we get a new ss with each iteration that is naturally reset & ready).


12c Duthomhas) I have read in two books now that one should check that the file is open before using it and that one should close it to release system resources. I understand that your file "f" will go out of scope & because of RAII the file will close when the program ends. But take this scenario, if you don't close out the file and do something more complicated down the line, which then causes the program to terminate abruptly, what happens?

Wouldn't it have been better off to have closed the file before the program terminated due to some exception...ya know us newbs? Does the operating sys clean up ill terminated programs regardless (less dynamic memory)?

I also understand that the stringstream will fail with errors and no longer extract if the file cannot be opened/created. I also understand that the below code will either overwrite an existing file (truncate) or create a new file. But wouldn't it be better to check if the below file name was opened regardless? What if someone is on a small SSD and resources are getting thin and the file cannot be created?

I also do understand that you have your error checking upon opening the file, during
"if (!f)"

1
2
3
4
5
	std::ofstream(filename)
		<< std::fixed << std::setprecision(1)
		<< 48.4 << " " << 57.23 << '\n'
		<< 46.0 << " " << 50.0  << '\n'
		<< 78.3 << " " << 101.4 << '\n';


I also understand the rest of what you are telling me, again much thanks!
Last edited on
RE:Qs

one should check that the file is open before using it
You should always verify when necessary. Think it through for a moment: what is the consequence if the attempt to create/overwrite the file fails?

In this example... nothing.

  • Case 1: the file does not exist and cannot be created:
    --> attempts to open the file fail and the program terminates with no output.

  • Case 2: the file already exists and cannot be overwritten:
    --> attempts to open the file may succeed and output follows from the content of the extant file.

So yes, it may indeed make a difference. If failure to create/overwrite the file is itself an error state, then you should absolutely test it!

Remember, the error state of the stream prevents further I/O attempts until cleared. Should the underlying problem not have been resolved, the error will resurface.

So if you attempt to open and read from a file AND that attempt fails, nothing useful will happen, since all I/O attempts will fail. Use this to your advantage.


Wouldn't it have been better off to have closed the file before the program terminated due to some exception
That’s not just a newbie question — seasoned developers should be asking the same question.

The trick is to be aware of your lexical context. For the small program exampled here, the lexical context closes at the end of main().

For more complex programs, you should indeed make sure a file is properly closed as soon as you are done with it.

The way to do this is... do file I/O from a function! This follows a philosophy much older than C++ called “structured programming”, where we break things down into small chunks.

For example, a large program may look something like:

1
2
3
4
5
int main()
{
  auto image = load_image_from_file( filename );
  ...
}

The file stream and all file I/O necessary to read an image is wrapped very nicely in that function, which may very well look something like:

1
2
3
4
5
6
ImageData load_image_from_file( const std::string & filename )
{
  std::ifstream f( filename );
  if (!f) throw error( "Could not open image file \"", filename, "\"" );
  return load_image_from_stream( f );
}

The file is opened, checked for error, and passed along to the worker function. When the worker function terminates, so does the local file stream object: the file is closed and image data is returned.


hat if someone is on a small SSD and resources are getting thin and the file cannot be created?
If you’re working with such thin resources, you should be checking whether the file already exists before even trying to create it. But again, attempting to open a non-existant file will not hurt R/W limits.

Again, proper function is the more important factor: how should the program behave if the file already exists? Is failure to overwrite an error? Etc. Answer those questions and your code writes itself.


Glad to be of help! :O)
Last edited on
1
2
3
4
5
std::ofstream(filename)
		<< std::fixed << std::setprecision(1)
		<< 48.4 << " " << 57.23 << '\n'
		<< 46.0 << " " << 50.0  << '\n'
		<< 78.3 << " " << 101.4 << '\n';


I see now. After all that request from my sources for checking, I was under the impression this will crash the program. I just did a test and blocked all privileges for that file, and as you said the ifstream "f" handled the error & exits. So basically the .fail() & .bad() bits are set and none of the insertion op << above are allowed to happen...but it does not crash the program.

Also, I have to get used to placing variables (as appropriate) within the while() loop and getting more comfortable with letting the compiler optimize this. The snippet from the book has those variables outside the while loop and I am prone to follow suite anally and not trusting the compiler to do this all the time.


12d Duthomhas) Let me ask you something. If the line below replaces the internal buffer with the string, shouldn't it internally just reset the bit flags as well? I could understand someone saying that you might have needed those errors from before, but then just use them before sending the new line. Do you know why they couldn't have just automatically reset the error bits when sending a new line?

 
std::istringstream ss(line);


I could understand the code below not automatically resetting the error bits, because you are appending to the stream. But why not the above?

 
ss << someString;




12e Duthomhas) This reminds me, this is also in the book. It looks like they are suggesting terminating the program when the cin stream is bad. Is this what is done? Or if the cin was trivial in only getting the user name and you never relied on it again, can you bypass using the cin again (and use a default name) and continue your program? Or does a bad cin indicate something more sinister?

1
2
3
4
5
6
7
8
9
10
11
12
13
		if (cin.good())
			break;
		else if (cin.fail())
		{
			cout << "Invalid number! Try again.\n";
			cin.clear();
			cin.ignore(numeric_limits<streamsize>::max(), '\n');
		}
		else if (cin.bad())
		{
			cout << "BAD STREAM...EXITING PROGRAM....GOODBYE!!!\n";
			return 0;
		}
Note that the cin stream may NOT refer to the keyboard (and cout may not refer to the display) but can refer to a file input/output if stream re-direction has been used with the command. See https://ss64.com/nt/syntax-redirection.html

Also, that code above is not correct. .fail() checks whether either fail bit or bad bit is set whereas .bad() just checks if the bad bit is set. So you should check for .bad() before checking for .fail() if you're making a distinction between the two. In the above code, the check for .bad() will never be undertaken.

The fail bit is set on a logical stream error (ie trying to read an invalid number) and bad bit is set when there is a read error on the operation (which usually means a physical error with the device). Often you don't make the distinction and treat logical error and read error the same (hence why .fail() checks both bits).
You can even do this:

1
2
3
// Attempt to read a line and print the values we got
if (double low, high; ss >> low >> high)
    std::cout << std::setw(8) << low << std::setw(8) << high << "\n";

The book has these snippets of code


Which book are you using?
It looks like they are suggesting terminating the program when the cin stream is bad. Is this what is done?

It looks like the program has to get a valid number from "user" or else it cannot continue at all. The state of the input stream is not the relevant bit -- "got number" / "can't get number" is what dictates continuation.

Or if the cin was trivial in only getting the user name and you never relied on it again, can you bypass using the cin again (and use a default name) and continue your program?

Again, the state of cin is not important, because the program will have a name (string) to continue with regardless of whether the user supplies one. If the program does later require more input, then you have to decide whether to run up to that or abort already here?
the book has those variables outside the while loop
This is a common thing in many books that follow an older C-style design.

Back when we were all wee laddies (or not yet born!) C required all variables to be declared before any statements. So:

1
2
3
4
5
6
7
8
9
10
11
12
13
void my_func()
{
  int    x;
  char * s;
  float  f;

  for (x = 0;  x < 10;  x++)
  {
    s = ...;
  }

  f = ...;
}

(Even back then you could use curly braces {,} to create a local lexical context and keep stuff together, but few people ever did...)

It appears your book is following that way of thinking. It’s not... wrong, per se, but it isn’t the way modern C++ (and C!) should be written.

The thing to remember is that the compiler can do it better than you can. Hard to accept, but true. Let the compiler do its job.


If the line below replaces the internal buffer with the string, shouldn't it internally just reset the bit flags as well? [...]
You’d think so, but that’s not how the class was designed to function. I’m pretty sure there’s an actual reason for that. I don’t know what it is. It has never caused me any difficulties.


It looks like they are suggesting terminating the program when the cin stream is bad. Is this what is done?
Maybe. Depends on your program.

Or if the cin was trivial in only getting the user name and you never relied on it again, can you bypass using the cin again (and use a default name) and continue your program?
Sure. However you design your program to work.

Or does a bad cin indicate something more sinister?
Remember, standard input is just another input stream. A special one, yes, but a character file none-the-less.


Remember, all streams can have:

  • an end
  • access/privilege errors
  • data your program does not like

For standard input, the user can terminate it by pressing Ctrl+Z,Enter (Windows) or Ctrl+D (*nixen), or by simply redirecting a normal file from disk to it, or a piped file from another utility to it, etc.

If the file simply does not contain data you like that is something the program should handle.


 
  cin >> anything;

Part of the problem is that code like this is exampled almost everywhere, and it is almost always WRONG. (With capital everything.)

(The same holds true for scanf(), but I’ll leave that rant for another day.)

There are two issues:

  • Text files, and standard input specifically, are line based data.
  • C++ streams allow you to goober the state of the stream more or less permanently.

Use getline() to obtain a line of input from cin (or your ifstream), then use an istringstream to parse it.

Likewise, use an ostringstream to format data, then send the resulting string through cout (or your ofstream object).

This is because cin, cout, and cerr are global, shared resources. If you jack its state, some other part of your program may very well malfunction when attempting to use it.

Hence, use a local, temporary stringstream to do things like set persistent formatting preferences and simply pass the string data through the standard streams unmodified.

This, of course, requires a little more care when programming, and tends to make writing short examples harder in how-to books, websites, etc, so you see a lot of

1
2
3
4
5
  cout << hex << STUFF;

  ...

  /* end of program */

Instead of the more correct:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::ostream & operator << ( std::ostream & outs, Stuff stuff )
{
  std::ostringstream oss;
  oss << hex << stuff.value;
  return outs << oss.str();
}

...

int main()
{
  Stuff my_stuff;
  ...
  std::cout << my_stuff << "\n";
}


Meh. Enough ranting. :O)


seeplus wrote:
You can even do this:

1
2
3
// Attempt to read a line and print the values we got
if (double low, high; ss >> low >> high)
    std::cout << std::setw(8) << low << std::setw(8) << high << "\n";
I still don’t know how I feel about that. In one way it is definitely welcome — the ability to declare a variable in-line and use it is nice...

But it comes at the cost of obscure syntax. I mean, I know what it means and can read it just fine, but it still takes a second to process, where the following is just so much more straight-forward:

1
2
3
  double low, high;       // ah, variables! one named "low" and another "high"
  if (ss >> low >> high)  // hey, an "if" statement. Easy peasy!
    ...

If scope of those two variables is an issue, a pair of curly braces {,} fixes that easily enough and remains just as readable.

The only time I can imagine such a construct being actually useful is in old & crufty macro tricks where we currently have to use a do..while loop or a GCC compiler extension.

But then, I think I tend to write fairly clean code...

IDK.
Last edited on
Likewise, use an ostringstream to format data, then send the resulting string through cout (or your ofstream object).

Combine using ostringstream with C++20's <format> library and you can really prettify up output as needed/wanted.

If one's compiler doesn't yet have <format> working or the code base is pre-C++20 there is the {fmt} 3rd party library. <format> is based on {fmt}. There's even an older version that works with C++98!

https://fmt.dev/latest/index.html

Pages: 1... 56789... 13